So I've been having a tough time finding documentation on exactly how sockets should behave when you have 2, both bound to the same endpoint, but one of them is also connected to a remote endpoint.
- The sockets are UDP IPv4
- Running in .net core 2.2/3 on linux x64
What I have been able to gather from various sources, is that the connected socket should always and only receive datagrams from the endpoint it is connected to and the "unconnected" socket will receive everything else.
I vaguely remember reading that the kernel socket implementation assigns "points" to each socket when a dgram arrives, and the socket with the higher score (most specific route) gets the data. If two socket get the same score, the dgrams are "load balanced" between the sockets.
I put together a small test:
class Program
{
static void Main(string[] args)
{
var localEp = new IPEndPoint(IPAddress.Loopback, 1114);
var remoteEp = new IPEndPoint(IPAddress.Loopback, 1115);
//Socket bound to local EP, not connected should receive from everyone
var notConnected = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
notConnected.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
notConnected.Bind(localEp);
//Socket bound and connected should receive from only it's remote EP
var connected = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
connected.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
connected.Bind(localEp);
connected.Connect(remoteEp);
var notConnectedTask = Task.Run(() => Receive(notConnected, "Not Connected"));
var connectedTask = Task.Run(() => Receive(connected, "Connected"));
//Remote socket to send to connected socket
var remote1 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
remote1.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
remote1.Bind(remoteEp);
remote1.Connect(localEp);
//Remote socket to send to notConnected socket
var remote2 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
remote2.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
remote2.Bind(new IPEndPoint(IPAddress.Loopback, 1116));
remote2.Connect(localEp);
for (int i = 0; i < 10; i++)
{
//This should be received by connected socket only
remote1.Send(Encoding.Default.GetBytes($"message {i} to connected socket"));
//This should be received by unconnected socket only
remote2.Send(Encoding.Default.GetBytes($"message {i} to notConnected socket"));
}
remote1.Send(Encoding.Default.GetBytes("end"));
remote2.Send(Encoding.Default.GetBytes("end"));
Task.WaitAll(notConnectedTask, connectedTask);
}
public static void Receive(Socket sock, string name)
{
EndPoint ep = new IPEndPoint(IPAddress.Any, 0);
var buf = new byte[1024];
Console.WriteLine($"{name} is listening...");
while (true)
{
var rcvd = sock.ReceiveFrom(buf, ref ep);
var msg = Encoding.Default.GetString(buf.Take(rcvd).ToArray());
Console.WriteLine($"{name} => {msg}");
if (msg.SequenceEqual("end"))
return;
}
}
}
To My surprise and chagrin, the result was nothing close to what I expected:
Connected is listening...
Not Connected is listening...
Connected => message 0 to connected socket
Connected => message 0 to notConnected socket
Connected => message 1 to connected socket
Connected => message 1 to notConnected socket
Connected => message 2 to connected socket
Connected => message 2 to notConnected socket
Connected => message 3 to connected socket
Connected => message 3 to notConnected socket
Connected => message 4 to connected socket
Connected => message 4 to notConnected socket
Connected => message 5 to connected socket
Connected => message 5 to notConnected socket
Connected => message 6 to connected socket
Connected => message 6 to notConnected socket
Connected => message 7 to connected socket
Connected => message 7 to notConnected socket
Connected => message 8 to connected socket
Connected => message 8 to notConnected socket
Connected => message 9 to connected socket
Connected => message 9 to notConnected socket
Connected => end
Not only did the notConnected socket not receive anything, but the connected socket got everything...
So neither of my expectations seem true. No load balancing, and no point system.
I had posted a comment on SO asking this question and got a reply almost confirming my expectation:
I think if one is connected to a remote endpoint, then all datagrams originating from that remote endpoint will end up at the connected socket. The unconnected one would only catch datagrams from other remote endpoints.
And I also have an e-mail from the OpenSSL mailing group mostly confirming it as well...
I suppose the above test should answer my question definitively, but it just seems so wrong!
Perhaps I made a mistake in the code, or I'm just missing something. I'd appreciate a bit of guidance.
Am I completely wrong about how sockets work?
EDIT
So I just re-ran my test, exactly as above, and the result is almost the same, only the socket receiving the data is the "notConnected" socket.
Binding the notConnected socket after the connected socket also has no effect.