Skip to content

Commit f9637f1

Browse files
authored
fix SendTo with SocketAsyncEventArgs (#98134)
* fix SendTo with SocketAsyncEventArgs * feedback
1 parent f729653 commit f9637f1

File tree

5 files changed

+106
-9
lines changed

5 files changed

+106
-9
lines changed

src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,6 @@ public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socke
677677
Debug.Assert(saea.BufferList == null);
678678
saea.SetBuffer(MemoryMarshal.AsMemory(buffer));
679679
saea.SocketFlags = socketFlags;
680-
saea._socketAddress = null;
681680
saea.RemoteEndPoint = remoteEP;
682681
saea.WrapExceptionsForNetworkStream = false;
683682
return saea.SendToAsync(this, cancellationToken);
@@ -709,8 +708,17 @@ public ValueTask<int> SendToAsync(ReadOnlyMemory<byte> buffer, SocketFlags socke
709708
saea.SetBuffer(MemoryMarshal.AsMemory(buffer));
710709
saea.SocketFlags = socketFlags;
711710
saea._socketAddress = socketAddress;
711+
saea.RemoteEndPoint = null;
712712
saea.WrapExceptionsForNetworkStream = false;
713-
return saea.SendToAsync(this, cancellationToken);
713+
try
714+
{
715+
return saea.SendToAsync(this, cancellationToken);
716+
}
717+
finally
718+
{
719+
// detach user provided SA so we do not accidentally stomp on it later.
720+
saea._socketAddress = null;
721+
}
714722
}
715723

716724
/// <summary>

src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3095,14 +3095,22 @@ private bool SendToAsync(SocketAsyncEventArgs e, CancellationToken cancellationT
30953095
ArgumentNullException.ThrowIfNull(e);
30963096

30973097
EndPoint? endPointSnapshot = e.RemoteEndPoint;
3098-
if (e._socketAddress == null)
3098+
3099+
// RemoteEndPoint should be set unless somebody used SendTo with their own SA.
3100+
// In that case RemoteEndPoint will be null and we take provided SA as given.
3101+
if (endPointSnapshot == null && e._socketAddress == null)
30993102
{
3100-
if (endPointSnapshot == null)
3101-
{
3102-
throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "e.RemoteEndPoint"), nameof(e));
3103-
}
3103+
throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "e.RemoteEndPoint"), nameof(e));
3104+
}
31043105

3105-
// Prepare SocketAddress
3106+
if (e._socketAddress != null && endPointSnapshot is IPEndPoint ipep && e._socketAddress.Family == endPointSnapshot?.AddressFamily)
3107+
{
3108+
// we have matching SocketAddress. Since this is only used internally, it is ok to overwrite it without
3109+
ipep.Serialize(e._socketAddress.Buffer.Span);
3110+
}
3111+
else if (endPointSnapshot != null)
3112+
{
3113+
// Prepare new SocketAddress
31063114
e._socketAddress = Serialize(ref endPointSnapshot);
31073115
}
31083116

src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,12 @@ internal void FinishOperationSyncSuccess(int bytesTransferred, SocketFlags flags
923923
case SocketAsyncOperation.ReceiveFrom:
924924
// Deal with incoming address.
925925
UpdateReceivedSocketAddress(_socketAddress!);
926-
if (_remoteEndPoint != null && !SocketAddressExtensions.Equals(_socketAddress!, _remoteEndPoint))
926+
if (_remoteEndPoint == null)
927+
{
928+
// detach user provided SA as it was updated in place.
929+
_socketAddress = null;
930+
}
931+
else if (!SocketAddressExtensions.Equals(_socketAddress!, _remoteEndPoint))
927932
{
928933
try
929934
{

src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,35 @@ public void SendToAsync_NullAsyncEventArgs_Throws_ArgumentNullException()
173173
public sealed class SendTo_Task : SendTo<SocketHelperTask>
174174
{
175175
public SendTo_Task(ITestOutputHelper output) : base(output) { }
176+
177+
[Theory]
178+
[InlineData(false)]
179+
[InlineData(true)]
180+
public async Task SendTo_DifferentEP_Success(bool ipv4)
181+
{
182+
IPAddress address = ipv4 ? IPAddress.Loopback : IPAddress.IPv6Loopback;
183+
IPEndPoint remoteEp = new IPEndPoint(address, 0);
184+
185+
using Socket receiver1 = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
186+
using Socket receiver2 = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
187+
using Socket sender = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
188+
189+
receiver1.BindToAnonymousPort(address);
190+
receiver2.BindToAnonymousPort(address);
191+
192+
byte[] sendBuffer = new byte[32];
193+
var receiveInternalBuffer = new byte[sendBuffer.Length];
194+
ArraySegment<byte> receiveBuffer = new ArraySegment<byte>(receiveInternalBuffer, 0, receiveInternalBuffer.Length);
195+
196+
197+
await sender.SendToAsync(sendBuffer, SocketFlags.None, receiver1.LocalEndPoint);
198+
SocketReceiveFromResult result = await ReceiveFromAsync(receiver1, receiveBuffer, remoteEp).WaitAsync(TestSettings.PassingTestTimeout);
199+
Assert.Equal(sendBuffer.Length, result.ReceivedBytes);
200+
201+
await sender.SendToAsync(sendBuffer, SocketFlags.None, receiver2.LocalEndPoint);
202+
result = await ReceiveFromAsync(receiver2, receiveBuffer, remoteEp).WaitAsync(TestSettings.PassingTestTimeout);
203+
Assert.Equal(sendBuffer.Length, result.ReceivedBytes);
204+
}
176205
}
177206

178207
public sealed class SendTo_CancellableTask : SendTo<SocketHelperCancellableTask>

src/libraries/System.Net.Sockets/tests/FunctionalTests/SocketAsyncEventArgsTest.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,5 +895,52 @@ void CreateSocketAsyncEventArgs() // separated out so that JIT doesn't extend li
895895
return cwt.Count() == 0; // validate that the cwt becomes empty
896896
}, 30_000));
897897
}
898+
899+
[Theory]
900+
[InlineData(false)]
901+
[InlineData(true)]
902+
public async Task SendTo_DifferentEP_Success(bool ipv4)
903+
{
904+
IPAddress address = ipv4 ? IPAddress.Loopback : IPAddress.IPv6Loopback;
905+
IPEndPoint remoteEp = new IPEndPoint(address, 0);
906+
907+
using Socket receiver1 = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
908+
using Socket receiver2 = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
909+
using Socket sender = new Socket(address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
910+
911+
receiver1.BindToAnonymousPort(address);
912+
receiver2.BindToAnonymousPort(address);
913+
914+
byte[] sendBuffer = new byte[32];
915+
var receiveInternalBuffer = new byte[sendBuffer.Length];
916+
ArraySegment<byte> receiveBuffer = new ArraySegment<byte>(receiveInternalBuffer, 0, receiveInternalBuffer.Length);
917+
918+
using SocketAsyncEventArgs saea = new SocketAsyncEventArgs();
919+
ManualResetEventSlim mres = new ManualResetEventSlim(false);
920+
921+
saea.SetBuffer(sendBuffer);
922+
saea.RemoteEndPoint = receiver1.LocalEndPoint;
923+
saea.Completed += delegate { mres.Set(); };
924+
if (sender.SendToAsync(saea))
925+
{
926+
// did not finish synchronously.
927+
mres.Wait();
928+
}
929+
930+
SocketReceiveFromResult result = await receiver1.ReceiveFromAsync(receiveBuffer, remoteEp).WaitAsync(TestSettings.PassingTestTimeout);
931+
Assert.Equal(sendBuffer.Length, result.ReceivedBytes);
932+
mres.Reset();
933+
934+
935+
saea.RemoteEndPoint = receiver2.LocalEndPoint;
936+
if (sender.SendToAsync(saea))
937+
{
938+
// did not finish synchronously.
939+
mres.Wait();
940+
}
941+
942+
result = await receiver2.ReceiveFromAsync(receiveBuffer, remoteEp).WaitAsync(TestSettings.PassingTestTimeout);
943+
Assert.Equal(sendBuffer.Length, result.ReceivedBytes);
944+
}
898945
}
899946
}

0 commit comments

Comments
 (0)