WinSock, identifying a packet

Started by
46 comments, last by hplus0603 13 years ago
Hi, I have some trouble figuring out how to identify which Struct a payload belongs to.

The client can send a packet containing either a info or chat struct.

Struct INFO{UINT ID; float x, y, z;}
Struct Chat{UINT ID; UINT tag; string text;}


How does the server analyse the first UINT to determine which struct the recived packet belongs to then copy the packet into that struct?


Struct Test{UINT ID; /*more stuff here?*/}

Test Recv;
recvfrom(Socket, (char*)&Recv, sizeof(Test), 0, (sockaddr*)&RemoteAddress, &SizeInt);

//process Recv....
//RecvChat = Recv....
//RecvInfo = Recv.....

Switch(Recv.ID){
case: 0
chat(RecvChat);
break;
case: 1
info(RecvInfo);
break;
}
Advertisement
Two options:

Size is based on ID. Read just the ID, then use that to look up the size. Then read the remaining bytes.

Next option: Transmit the size of packet. The first marker is the size of the packet, not the ID. Read the size first, then read the remaining bytes.



While basing it on the ID is a valid option, it is not easily extended. If you get a packet you don't already know your system will crash. If you need to expand the size of a type of packet you cannot mix old and new versions. It is generally easiest to prepend the size of all variable-sized blocks of data.

For example, all serialized packets could be of the form:

{
16-bit size (or 32-bit size if you can imagine having bigger packets)
32-bit packet type (since that's what you wanted to use)
32-bit packet sequence number (not necessary but extremely useful for debugging)
... packet-type specific data ...
}
If I recvfrom() the packet is taken off the input queue. And I have no idea how long the payload is, data may be lost or I can go overkill and read with a Char array[4096] buffer?

To get a "taste" of the payload I got to use the MSG_PEEK flag first to find the size? Then read the first UNIT to get the ID?

So two calls to recvfrom() to first get the size(PEEK), then copy the payload (no flag)?

so far I got:


Struct InfoPayload{UINT ID; float x, y, z;}
Struct ChatPayload{UINT ID; UINT tag; string text;}
Struct Test{UINT ID;}

Test Recv;
int PayloadSize = recvfrom(Socket, (char*)&Recv, sizeof(Test), MSG_PEEK, (sockaddr*)&RemoteAddress, &SizeInt);

Char Buffer[PayloadSize];
recvfrom(Socket, &Buffer, PayloadSize, 0, (sockaddr*)&RemoteAddress, &SizeInt);

// then check ID with switch...?
Switch(Recv.ID){
case: 0
chat(Buffer);
break;
case: 1
info(Buffer);
break;
}



Or should I go:


Struct InfoPayload{UINT ID; float x, y, z;}

Struct ChatPayload{UINT ID; UINT tag; string text;}

Struct Test{UINT ID;}



Test Recv;

recvfrom(Socket, (char*)&Recv, sizeof(Test), MSG_PEEK, (sockaddr*)&RemoteAddress, &SizeInt);

Switch(Recv.ID){
case: 0
ChatPayload RecvChat;
recvfrom(Socket, (char*)&RecvChat, sizeof(ChatPayload), 0, (sockaddr*)&RemoteAddress, &SizeInt);
chat(RecvChat);
break;
case: 1
InfoPayload RecvInfo;
recvfrom(Socket, (char*)&RecvInfo, sizeof(InfoPayload), 0, (sockaddr*)&RemoteAddress, &SizeInt);
info(RecvInfo);
break;
}



Can you please provide some code?
I have read multiple people advising against MSG_PEEK. It is an unneccessary call. Just provide a buffer that is big enough to hold the largest packet your application is going to produce, then read the next packet. You are going to need it anyway! When you receive it, look at the first byte or wherever your descriptor is and act accordingly. Fin!

Edit:
Okay I just read your last post. Well I guess this right here is the one exceptional case where raw type-casting is preferrable. I would just read the packet in a char buffer and cast it to the right struct.
Another way to produce/read packets is using bitstreams, scrapping the use of different structs alltogether. That way you would wrap your incoming lump of data in a bitstream class that provides functions like readInt(), readFloat(), unpackVector3(), readBit(), etc. Good thing about this is, you can easily add compression functionality to the bitstream class.

Edit²:
Look up WSAIoctl() and the flag FIONREAD. It will return the size of the first packet in the queue.
Thanks for the tip.

How would you extract the first UINT from a Char buffer[1400]?


Struct Test{UINT ID;}
Test Rcv = (Test)buffer;
UINT ID = Rcv.ID;


or


char *myChar = buffer[0]+buffer[1]+buffer[2]+buffer[3];
WORD ID = reinterpret_cast<WORD>(myChar);


or


char *myChar = buffer[0]+buffer[1]+buffer[2]+buffer[3];
UINT ID = reinterpret_cast<UINT>(myChar);


Or some other thing?

Once I get the ID, I would know for example buffer[0]-buffer[32] have valid information. Could I then cast the buffer with 1400 bytes into a 32 byte Struct? would the remainding bytes be ignored? Will this take more time then the MSG_PEEK apporch?
Be warned that casting from a raw char buffer into a struct has many pitfalls and is generally highly unadvisable. It makes your implementation very brittle and susceptible to certain classes of simple corrupted-data denial of service attacks, for instance. Worse, it can lead to pretty easy arbitrary-code-execution attacks, meaning that your software can be used to completely take over a computer that's running it.

The potential problems include, but are by no means limited to:

  • Packing and padding differences between clients (structures may not share identical byte-for-byte layouts)
  • Endianness differences between clients (individual bytes/words/etc. may not share bit order across platforms)
  • Bit patterns for things like floating point types may differ across platforms
  • Only POD structures may be safely used; anything with a virtual member function, for instance, will cause corruption
  • You cannot use any pointer types in your packet structures, because what's a valid pointer on one client probably isn't on a different client
  • Ditto for anything like a C++SL string, container, or other nontrivial class; you have to manually serialize that sort of stuff


Look into serialization, particularly network serialization. This FAQ is a decent place to start.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Extracting the first UINT is easy.


char buffer[1000];
UINT first = *(UINT*)buffer;


However ApochPIQ is right. Pointer typecasting can be very dangerous if you're not absolutely sure what you are doing. Also, sending structs over the net is pretty easy to get you started but sooner or later you should switch to a different serialization mechanism.
Thanks alot for the comments. I think I will stick to plain old data structs for now. I dont need pointers or that stuff. I will read 1400 bytes always so I dont have to MSG_PEEK. (which is faster and safe?)


Struct InfoPayload{UINT ID; float x, y, z;}
Struct ChatPayload{UINT ID, tag, TxtLen; char Text[1200];}

Char Buffer[1400];
UINT ID;

while(true){
recvfrom(Socket, Buffer, 1400, 0, (sockaddr*)&RemoteAddress, &SizeInt);
ID = *(UINT*)Buffer;

Switch(ID){
case: 0
info(*(InfoPayload*)Buffer, RemoteAddress);
break;
case: 1
chat(*(ChatPayload*)Buffer, RemoteAddress);
break;
default:
InvalidPacket(Buffer, RemoteAddress);
break;
}

}
You need to look at the return value of recvfrom(). It returns the size of the packet actually received, 0 if no packet was received, or negative values if an error occurred.

Remember, there is more than your game on the internet. If you happen to share a port with an existing service, you could easily receive arbitrary data. More seriously, you should be able to deal with a hacker constructing packets designed to crash your game, corrupt your game data (possibly to the advantage of the hacker) or even to remotely compromise the system running the server (arbitrary code execution).

You need to be super-paranoid when parsing packets.
Yea, there will be a filtering layer in info() and chat() from which IPs may be banned for flooding or other detection methods. And after that another layer of handshaking/authentication for each IP. I just need a framework for sending and recieving different structs.

I though the return value only was valid for when MSG_PEEK was flagged, my mistake.

This topic is closed to new replies.

Advertisement