Regression from .net 7.
As part of managed implementation of QUIC, I have QuicSocketContext, which uses Socket.ReceiveFromAsync (for servers) and SocketReceiveAsync (for clients) to receive UDP datagrams. After rebasing the project onto latest main, server started receiving wrong port and thus ends up sending datagrams to itself, instead of to the clients.
Minimized repro code:
usingSystem.Net.Http;usingSystem.Security.Authentication;usingSystem.Buffers;usingSystem.Diagnostics;usingSystem.Net.Sockets;usingSystem.Net;usingSystem.Threading;varserverListeningAddress=newIPEndPoint(IPAddress.Loopback,5000);System.Console.WriteLine($"Starting server on {serverListeningAddress}");varserver=newQuicSocketContext(serverListeningAddress,null,true);server.Start();System.Console.WriteLine($"Server started on {server.LocalEndPoint}");varclient=newQuicSocketContext(null,server.LocalEndPoint,false);client.Start();System.Console.WriteLine($"===== Sending first datagram =====");client.SendDatagram(newDatagramInfo(newbyte[]{1,2,3},3,server.LocalEndPoint));Thread.Sleep(-1);internalreadonlyrecordstructDatagramInfo(byte[]Buffer,intLength,EndPointRemoteEndpoint);internalclassQuicSocketContext{privatereadonlyEndPoint?_localEndPoint;privatereadonlyEndPoint?_remoteEndPoint;privatereadonlybool_isServer;privatebool_started;privatereadonlySocket_socket=newSocket(SocketType.Dgram,ProtocolType.Udp);privatereadonlySocketAsyncEventArgs_socketReceiveEventArgs=newSocketAsyncEventArgs();publicQuicSocketContext(EndPoint?localEndPoint,EndPoint?remoteEndPoint,boolisServer){_localEndPoint=localEndPoint;_remoteEndPoint=remoteEndPoint;_isServer=isServer;SetupSocket(localEndPoint,remoteEndPoint);_socketReceiveEventArgs.Completed+=(sender,args)=>OnReceiveFinished(args);}privatevoidSetupSocket(EndPoint?localEndPoint,EndPoint?remoteEndPoint){if(localEndPoint!=null){_socket.Bind(localEndPoint);}if(remoteEndPoint!=null){_socket.Connect(remoteEndPoint);}}publicIPEndPointLocalEndPoint=>(IPEndPoint)_socket.LocalEndPoint!;publicvoidStart(){if(_started){return;}_started=true;varargs=_socketReceiveEventArgs;while(!ReceiveFromAsync(args)){// this should not really happen, as the socket should be just opened, but we want to be sure and don't// miss any incoming datagramsOnDatagramReceived(ExtractDatagram(args));}}privateDatagramInfoExtractDatagram(SocketAsyncEventArgsargs){returnnewDatagramInfo(args.Buffer!,args.SocketError==SocketError.Success?args.BytesTransferred:0,args.RemoteEndPoint??_remoteEndPoint!);}privatevoidOnReceiveFinished(SocketAsyncEventArgsargs){System.Console.WriteLine($"{(_isServer?"Server":"Client")}: OnReceiveFinished, RemoteEndPoint = {args.RemoteEndPoint}");boolpending;do{DatagramInfodatagram=ExtractDatagram(args);// immediately issue another async receiveSystem.Console.WriteLine($"{(_isServer?"Server":"Client")}: Starting next receive");pending=!ReceiveFromAsync(args);OnDatagramReceived(datagram);}while(pending);}protectedvoidOnDatagramReceived(inDatagramInfodatagram){System.Console.WriteLine($"{(_isServer?"Server":"Client")}: processing datagram from {datagram.RemoteEndpoint}");Thread.Sleep(1000);SendDatagram(datagram);ArrayPool.Return(datagram.Buffer);}internalArrayPool<byte>ArrayPool{get;}=ArrayPool<byte>.Shared;privateboolReceiveFromAsync(SocketAsyncEventArgsargs){// use fresh buffer for each receive, since the previous one is still being processedvarbuffer=ArrayPool.Rent(1200);args.SetBuffer(buffer,0,buffer.Length);if(_remoteEndPoint!=null){System.Console.WriteLine($"Client: Socket.ReceiveAsync");return_socket.ReceiveAsync(args);}args.RemoteEndPoint=_localEndPoint!;System.Console.WriteLine($"Server: Socket.ReceiveFromAsync with RemotEndPoint = {_localEndPoint}");return_socket.ReceiveFromAsync(args);}internalvoidSendDatagram(inDatagramInfodatagram){if(_remoteEndPoint!=null){// connected socket - > Socket.SendSystem.Console.WriteLine($"Client: Send over connected socket to {_remoteEndPoint}");_socket.Send(datagram.Buffer.AsSpan(0,datagram.Length),SocketFlags.None);}else{System.Console.WriteLine($"Server: SendTo {datagram.RemoteEndpoint}");_socket.SendTo(datagram.Buffer,0,datagram.Length,SocketFlags.None,datagram.RemoteEndpoint);}}}.NET 7 output:
Starting server on 127.0.0.1:5000
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server started on [::ffff:127.0.0.1]:5000
Client: Socket.ReceiveAsync
===== Sending first datagram =====
Client: Send over connected socket to [::ffff:127.0.0.1]:5000
Server: OnReceiveFinished, RemoteEndPoint = [::ffff:127.0.0.1]:57206
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from [::ffff:127.0.0.1]:57206
Server: SendTo [::ffff:127.0.0.1]:57206
Client: OnReceiveFinished, RemoteEndPoint =
Client: Starting next receive
Client: Socket.ReceiveAsync
Client: processing datagram from [::ffff:127.0.0.1]:5000
Client: Send over connected socket to [::ffff:127.0.0.1]:5000
Server: OnReceiveFinished, RemoteEndPoint = [::ffff:127.0.0.1]:57206
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from [::ffff:127.0.0.1]:57206
Server: SendTo [::ffff:127.0.0.1]:57206
Client: OnReceiveFinished, RemoteEndPoint =
Client: Starting next receive
Client: Socket.ReceiveAsync
Client: processing datagram from [::ffff:127.0.0.1]:5000
Client: Send over connected socket to [::ffff:127.0.0.1]:5000
Server: OnReceiveFinished, RemoteEndPoint = [::ffff:127.0.0.1]:57206
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from [::ffff:127.0.0.1]:57206
....
.NET 8 output, manual emphasis on first wrong line
Starting server on 127.0.0.1:5000
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server started on [::ffff:127.0.0.1]:5000
Client: Socket.ReceiveAsync
===== Sending first datagram =====
Client: Send over connected socket to [::ffff:127.0.0.1]:5000
Server: OnReceiveFinished, RemoteEndPoint = [::ffff:127.0.0.1]:65505
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from [::ffff:127.0.0.1]:65505
Server: SendTo [::ffff:127.0.0.1]:65505
Client: OnReceiveFinished, RemoteEndPoint =
Client: Starting next receive
Client: Socket.ReceiveAsync
Client: processing datagram from [::ffff:127.0.0.1]:5000
Client: Send over connected socket to [::ffff:127.0.0.1]:5000
Server: OnReceiveFinished, RemoteEndPoint = 127.0.0.1:5000 // <---- here, server starts talking to itself because provided RemoteEndPoint is incorrect.
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from 127.0.0.1:5000
Server: SendTo 127.0.0.1:5000
Server: OnReceiveFinished, RemoteEndPoint = 127.0.0.1:5000
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from 127.0.0.1:5000
Server: SendTo 127.0.0.1:5000
Server: OnReceiveFinished, RemoteEndPoint = 127.0.0.1:5000
Server: Starting next receive
Server: Socket.ReceiveFromAsync with RemotEndPoint = 127.0.0.1:5000
Server: processing datagram from 127.0.0.1:5000
...
Regression from .net 7.
As part of managed implementation of QUIC, I have QuicSocketContext, which uses Socket.ReceiveFromAsync (for servers) and SocketReceiveAsync (for clients) to receive UDP datagrams. After rebasing the project onto latest main, server started receiving wrong port and thus ends up sending datagrams to itself, instead of to the clients.
Minimized repro code:
.NET 7 output:
.NET 8 output, manual emphasis on first wrong line