TCP stalls when socket is spammed


#21

Did you remove

rc = recv(SockTCPL, HosRxDat.bad, &len, &(HosRxIp.ul), &port);

from the code? It may cause 0 size to be “received”, and thus dup ack packet sent.

And by the way, what socket number command

W5200RegRead(Sn_RX_RSR0, &uc0);

assumes?


#22

No, I didn’t remove the recv() call…I thought it was needed to effect the change in the pointers.

Sn_RX_RSR0 is defined with the socket number included in it (0x4326). The read just goes to an address; it doesn’t know about sockets. Socket 3 is the only one that needs this special attention.


#23

Ok, remove recv() and try. Note that what we do now is a kind of “low level hacking”, ideally you must use recv() call which moves data (and which you want to avoid under your low performance circumstances). Do not forget to put comments into your code, as you definitely forget everything in a week or two.


#24

Can’t get it working. Here’s the clear routine:

void W5200ClearRxBuffer()
{
	__monitor char W5200RegRead(u16 adr); 
	__monitor void W5200RegWrite(u16 adr,   
                          char data);	

const u16 Sn_MR = 0x4300;
const u16 Sn_RX_RSR0 = 0x4326;
const u16 Sn_RX_RSR1 = 0x4327;
const u16 Sn_RX_RD0 = 0x4328;
const u16 Sn_RX_RD1 = 0x4329;
const u16 Sn_CR = 0x4301;
const u8 RECV = 0x40;

u16 len = APGmPktZ;
u16 port = LANCLibPO;

u8 uc0;
u8 uc1;
u16 rsr;
u16 rd;

// 2018 11 21 mzimmers:
// temporarily read Sn_MR to see whether
// no delayed ACK is set in bit 5.
// note: it is. we might want to change this.
uc0 = W5200RegRead(Sn_MR);

uc0 = W5200RegRead(Sn_RX_RSR0);
uc1 = W5200RegRead(Sn_RX_RSR1);
rsr = (uc1 << 8) | uc0;

uc0 = W5200RegRead(Sn_RX_RD0);
uc1 = W5200RegRead(Sn_RX_RD1);
rd = (uc1 << 8) | uc0;

rd += rsr;

uc0 = rd & 0xff;
uc1 = rd >> 8;

W5200RegWrite(Sn_RX_RD0, uc0);
W5200RegWrite(Sn_RX_RD1, uc1);

// it isn't clear yet whether the lines below are necessary.
// commenting them out for now.
//W5200RegWrite(Sn_CR, RECV); 
//recv(SockTCPL, HosRxDat.bad, &len, &(HosRxIp.ul), &port);	

}


#25

How it does not work? You must give details. Code looks ok. Sn_MR read is spare at the beginning (as your comment states), and I guess there’s no problem with rd overflow after addition (u16 type, MSB will be discarded).

You are using socket #3, right?

Edit: no, code is not ok! Manual says:

When reading this register, user should read upper byte(0x4026, 0x4126, 0x4226, 0x4326, 0x4426, 0x4526, 0x4626, 0x4726) first, and lower byte(0x4027, 0x4127, 0x4227, 0x4327, 0x4427, 0x4527, 0x4627, 0x4727) later to get the correct value.

Note that upper byte of RSR will be in uc0, and lower byte will be in uc1. Thus you must have

rsr = (uc0 << 8) | uc1;

and same for RD.


#26

Hi Eugeny -

  1. By “not working” I meant that the stated problem persists: as the flood of incoming TCP messages rises, the TCP window steadily shrinks to 0, at which point the socket is unresponsive.

  2. I made the changes you suggested, tried this with and without a final recv() call, and even eliminated the direct buffer manipulation in favor of a lone recv() call. Behavior is unchanged.

I’m beginning to think the problem is simply that the W5200 is so fast, and the CPU so slow by comparison, that the W5200 is just “outrunning” the CPU.

__monitor void W5200ClearRxBuffer()
{
const u16 Sn_RX_RSR0 = 0x4326;
const u16 Sn_RX_RSR1 = 0x4327;
const u16 Sn_RX_RD0 = 0x4328;
const u16 Sn_RX_RD1 = 0x4329;
const u16 Sn_CR = 0x4301;
const u8 RECV = 0x40;

const u16 len = APGmPktZ;
const u16 port = LANCLibPO;
__monitor char W5200RegRead(u16 adr); 
__monitor void W5200RegWrite(u16 adr, char data);	

u8 uc0; 
u8 uc1;
u16 rsr;
u16 rd;

uc0 = W5200RegRead(Sn_RX_RSR0);
uc1 = W5200RegRead(Sn_RX_RSR1);
rsr = (uc0 << 8) | uc1;

uc0 = W5200RegRead(Sn_RX_RD0);
uc1 = W5200RegRead(Sn_RX_RD1);
rd = (uc0 << 8) | uc1;

rd += rsr;

uc0 = rd >> 8;
uc1 = rd & 0xff;

W5200RegWrite(Sn_RX_RD0, uc0);
W5200RegWrite(Sn_RX_RD1, uc1);

W5200RegWrite(Sn_CR, RECV); 
recv(SockTCPL, HosRxDat.bad, (u16 *) &len, &(HosRxIp.ul), (u16 *) &port);

}


#27

I’m still trying to get this solved. I’d like to begin by merely discarding any data in the buffer. To do this, is it sufficient to set Sn_RX_RD = Sn_RX_WR, and then write a RECV into Sn_CR? Or is something else necessary?


#28

You do things properly except you must wait for Sn_CR to become zero after you write RECV command into it. You actually must wait Sn_CR to clear after any command write to it.

As soon as you can control “spamming” process stop it at some time and see if buffer will get freed.


#29

I still can’t update the pointers with the RECV command. Here’s my code:

uc0 = W5200RegRead(Sn_RX_RD0);
uc1 = W5200RegRead(Sn_RX_RD1);
rd = (uc0 << 8) | uc1;

uc0 = W5200RegRead(Sn_RX_WR0);
uc1 = W5200RegRead(Sn_RX_WR1);
wr = (uc0 << 8) | uc1;
	
rd += rsr;
uc0 = rd >> 8;
uc1 = rd & 0xff;

W5200RegWrite(Sn_RX_RD0, uc0);
W5200RegWrite(Sn_RX_RD1, uc1);

W5200RegWrite(Sn_CR, Sn_CMD_RECV);
do  
{
    uc0 = W5200RegRead(Sn_CR);  //  read command register
} while (uc0 != Sn_CR_DONE);    		//  until command complete

// for debugging only; just to look at the registers.
uc0 = W5200RegRead(Sn_RX_RSR0);
uc1 = W5200RegRead(Sn_RX_RSR1);
rsr = (uc0 << 8) | uc1;

uc0 = W5200RegRead(Sn_RX_RD0);
uc1 = W5200RegRead(Sn_RX_RD1);
rd = (uc0 << 8) | uc1;

uc0 = W5200RegRead(Sn_RX_WR0);
uc1 = W5200RegRead(Sn_RX_WR1);
wr = (uc0 << 8) | uc1;

rd and wr do not change. By the same token, rsr always seems to be 0. What might I be doing wrong?

Thanks.


#30

I assume there’s RSR read before the whole code you posted?

Of course after you perform RECV register RSR will contain 0, until socket receives some new data.

Or you say that RSR is zero before you perform RECV (the RSR read is not in the code you posted)?


#31

Hi Eugeny -

  1. Yes, there’s an RSR read prior to the code I posted. I occasionally have issues inserting code into my posts here.
  2. The RSR register is zero upon entry to this routine, but I think that’s a matter of how/where I call this routine, so it’s not a W5200 issue.
  3. I’m not sure I fully understand the behavior of the W5200. After performing a RECV, should I expect the RD/WR pointers to change? Or is only the RSR register altered?

#32

There’re three locations within the chip:
RD: location of the “user” pointer - the data which application must read;
WR: system pointer, the location where the chip’s network stack writes when data arrives;
RSR: it is simply WR-RD, and it can not be larger than RX buffer size.

Note that all registers are 16-bit, and must be treated as 16-bit values even if buffer size is 8KB.

Thus you do not touch WR under normal circumstances, it is managed by the chip. You only look at the RSR - amount of data received into the RX buffer, and at RD - the location to start reading data from.

Thus if you read RSR, and it appears 0 - this means that buffer is empty, there’s no data to read, and chip must respond with full window size free in its network packets.

Logically, if RSR is not 0, then RD is not equal to WR. Maximal value of RSR is RX buffer size.

When you write to RD pointer new value, and then issue RECV command, chip assumes that data between old RD location and new RD location is flushed by the application, and considers this space as available for new incoming data.

Therefore reading RSR, then assigning RD=RD+RSR and then RECV “frees” RSR bytes in the RX buffer, and decreases current value of RSR by RSR when it was previously read. Note that while you perform reading of RSR, then RD, then performing RD+RSR and assigning it back to RD time has passed, and actual RSR register value may increase. But it does not matter because RSR you originally read will always be <= current RSR, and there will be no buffer overrun, next time you read RSR you will get “remaining” value of RSR.

Then, very important note: when reading any 16-bit register (RD or RSR or anything else) you must read byte at +0 first, and byte at +1 second. Not critical for RD because it will not change dynamically without your RECV instruction, but very critical for RSR: when reading +0 first and +1 second you will be guaranteed to get value equal or less than current, and will never overrun the buffer. If you read other way around, you may get value bigger than current RSR, shifting RD ahead of WR with RECV command, and it will mess whole communication.

Example: RSR=0x01FF

Right order: you read 0x01 first, then RSR changes to 0x0200 (one more byte was received), and you read 0x00. Thus you have RSR=0x100, which, while is not “immediately correct”, should give you amount of bytes less than in the buffer, and on next read RSR will read as 0x100 and finally you will read all the data from the RX buffer.

Wrong order: you read 0xFF first, then RSR changes to 0x0200, and you read 0x02 for MSB. So you end up with RSR=0x02FF, and crash the socket communication.

I hope it is clearer now, and you can find at least the area for further investigation yourself - as I do not fully understand what is going within your design. You clearly do something incorrectly, but hard to say what it is exactly.


#33

Thanks for the explanation. I believe I have it working now. Here’s my modified code:

rsr = getW5200Reg16(Sn_RX_RSR0);
rd = getW5200Reg16(Sn_RX_RD0);
wr = getW5200Reg16(Sn_RX_WR0);
		
while (rsr > 0)
{
	rd += rsr;
	uc0 = rd >> 8;
	uc1 = rd & 0xff;
	
	W5200RegWrite(Sn_RX_RD0, uc0);
	W5200RegWrite(Sn_RX_RD1, uc1);
	
	W5200RegWrite(Sn_CR, Sn_CMD_RECV);
	do  
	{
		uc0 = W5200RegRead(Sn_CR);  //  read command register
	} 
	while (uc0 != Sn_CR_DONE);    		//  until command complete

	rsr = getW5200Reg16(Sn_RX_RSR0);
}

This seems to be effective at minimizing the impact of TCP-level DoS attacks that consist of nonsensical messages.

Thanks for all the help with this.