Advertisement

C# UDP Socket Receive - Identifying Sender via EndPoint?

Started by September 29, 2016 07:41 PM
5 comments, last by sufficientreason 8 years, 1 month ago

Hi there,

I'm reading from a UDP socket in C# as follows:


EndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
int receivedBytes =
  this.socket.ReceiveFrom(
    this.receiveBuffer,
    this.receiveBuffer.Length,
    SocketFlags.None,
    ref endPoint);

This provides me an EndPoint of the packet's sender. Does this EndPoint object uniquely identify the sender? If not, what would be a good scheme for doing so? A naive approach would be for a connecting client to generate a random uint and include it in all of that packets (handling the very very rare chance of collision), but perhaps there's a smarter way?

Passing 0 as port for a listening socket is bad. You need to pass in the port number that you want the remote client to connect/send to.

In general, an IP address + port identifies a unique process on the sending machine. However, if that process crashes, and re-starts, then you will still see the same ip address + port, from a new process on that same machine (or NAT gateway) that may or may not be the same user, and certainly doesn't remember what you told it in the past.

The best way to solve this is to include a randomly generated non-zero token (32-64 bits) at the front of the packet when you have verified a user. So, if I log in as "foobar" with the correct password, you randomly generate token 12345 for foobar at the given remote IP and port.
For the login request itself, make the token be 0. Then, you can tell that login requests are allowed a zero token; all other requests must have a token that matches the remote IP+port, and you can derive which user this is by storing a user ID as a value for the token+IP+port as key in your server.
enum Bool { True, False, FileNotFound };
Advertisement

That 0 isn't for binding the socket, it's just an empty initialization. TryReceive will fill out that object with both the IP and port received. The socket has already been bound by the time I call that.

In my system the first thing a new client sends to a server is a ConnectRequest packet, which it repeats at some frequency until it gives up or receives a ConnectAccept. Until it receives a ConnectAccept it won't respond to ping/heartbeat packets from the server. Similarly, if the server receives a ConnectRequest packet from a client it thinks is already connected, it will ignore it.

In the situation you describe, the client would drop and stop responding to server data or heartbeats. The server would ignore the restarted client's connect attempts until it timed out the old client connection. Then the client could re-establish and both client and server would know that this was a fresh start.

However, Is it possible for two users using the same NAT gateway to have the same IPEndpoint?

If you separately bind the socket to a known port, before receiving on it, that's fine.

If you rely on the kernel choosing some random port to bind to, which is the behavior for a socket with port 0 when you actually try to receive or send, then you have the problem of letting your clients know what port your kernel chose. That problem can be solved, but usually, you don't want to have to solve that, and use a well-defined port number.
enum Bool { True, False, FileNotFound };

Okay. It looks like I won't need to attach an ID to my packets in this case. If/when I start looking into encrypting/checksumming my packets I'll get unique client identification anyway based on the keys used.

It looks like I won't need to attach an ID to my packets in this case


That inherently makes your game less secure. Someone who can re-use the same remote IP and port (either through NAT shenanigans, or by running on the same computer as your game) can then hijack a session after it's ended, or even in the middle of the session, and issue commands in the name of the player who last logged in from that IP and port.
The attack might work like this: Let's say Timmy is using the family computer, and is called to dinner by mom. He kills the game. Right after, Sarah quickly runs a program on the computer that sends the "given all my gold to Sarah's character" command from the appropriate source IP:port, by running on her own laptop, and relying on the NAT gateway re-assigning the same remote IP:pot to her new session.
A token per connection is one level of security, serving a similar level of security as sequence numbers in TCP (that are randomly initialized.) They're not cryptographic, because they serve a different purpose.
enum Bool { True, False, FileNotFound };
Advertisement
Right now I'm focusing on making a fun game first. I'll be worrying about malicious activity a little later. One thing I should add is I'll be using the PlayFab matchmaker, which gives you an authentication token that needs to be passed to the server anyway on connection to validate the user's identity. Of course, it would be possible to hijack a player's session if I send that matchmaker token in plaintext.

A rough sketch of what I will eventually do is as follows:

1) Client connects independently to PlayFab via HTTPS, receives token.

2) Client connects to my server via either HTTPS or TLS.

3) Server provides some sort of fast key (symmetric?) to client over HTTPS/TLS.

4) Client disconnects from HTTPS/TLS with the key.

5) Client begins sending UDP packets to the server, including the PlayFab auth token.

5a) These UDP packets begin with a 4-byte CRC of the packet's header/data bytes and the network protocol version hashed together, followed by a header and the packet data.

5b) The client uses its key to encrypt the packet+CRC and send it to the server, including one packet with the PlayFab auth token.

6) The server does the same for any replies.

I'm not exactly sure the best way to do the initial key exchange, but I'm some way away from having to worry about that. Ideally I'd just do it over UDP but I'm not so sure how easy that will be vs. HTTPS/TLS. What this scheme should give me is tamper protection (via the CRC), secure client identification (if the CRC isn't right when the packet is decrypted, the client has the wrong key), version checking (to make sure an outdated client isn't connecting), and overall encryption for just 4 bytes.

This topic is closed to new replies.

Advertisement