diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index f3faf63b036df1..fb8367f258e81a 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -1356,7 +1356,7 @@ public int SendTo(byte[] buffer, int offset, int size, SocketFlags socketFlags, ThrowIfDisposed(); ValidateBufferArguments(buffer, offset, size); - ArgumentNullException.ThrowIfNull(remoteEP); + ValidateSendToEndpointAndState(remoteEP, nameof(remoteEP)); ValidateBlockingMode(); if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"SRC:{LocalEndPoint} size:{size} remoteEP:{remoteEP}"); @@ -1439,7 +1439,7 @@ public int SendTo(ReadOnlySpan buffer, SocketFlags socketFlags, EndPoint r if (!Socket.OSSupportsThreads) throw new PlatformNotSupportedException(); // TODO remove with https://github.com/dotnet/runtime/pull/107185 ThrowIfDisposed(); - ArgumentNullException.ThrowIfNull(remoteEP); + ValidateSendToEndpointAndState(remoteEP, nameof(remoteEP)); ValidateBlockingMode(); @@ -3280,6 +3280,11 @@ private bool SendToAsync(SocketAsyncEventArgs e, CancellationToken cancellationT throw new ArgumentException(SR.Format(SR.InvalidNullArgument, "e.RemoteEndPoint"), nameof(e)); } + if (endPointSnapshot is not null && !CanTryAddressFamily(endPointSnapshot.AddressFamily)) + { + throw new ArgumentException(SR.Format(SR.net_InvalidEndPointAddressFamily, endPointSnapshot.AddressFamily, _addressFamily), nameof(e)); + } + if (e._socketAddress != null && endPointSnapshot is IPEndPoint ipep && e._socketAddress.Family == endPointSnapshot?.AddressFamily) { // we have matching SocketAddress. Since this is only used internally, it is ok to overwrite it without @@ -3987,6 +3992,24 @@ private void ValidateReceiveFromEndpointAndState(EndPoint remoteEndPoint, string } } + // Validates that the remoteEndPoint is compatible with the Socket so that a SendTo call won't fail deep + // inside the OS PAL with a SocketError.InvalidArgument. This mirrors the address-family guard already + // applied to the ReceiveFrom/ReceiveMessageFrom methods so the SendTo family behaves consistently. + private void ValidateSendToEndpointAndState(EndPoint remoteEndPoint, string remoteEndPointArgumentName) + { + ArgumentNullException.ThrowIfNull(remoteEndPoint, remoteEndPointArgumentName); + + if (remoteEndPoint is DnsEndPoint) + { + throw new ArgumentException(SR.Format(SR.net_sockets_invalid_dnsendpoint, remoteEndPointArgumentName), remoteEndPointArgumentName); + } + + if (!CanTryAddressFamily(remoteEndPoint.AddressFamily)) + { + throw new ArgumentException(SR.Format(SR.net_InvalidEndPointAddressFamily, remoteEndPoint.AddressFamily, _addressFamily), remoteEndPointArgumentName); + } + } + // ValidateBlockingMode - called before synchronous calls to validate // the fact that we are in blocking mode (not in non-blocking mode) so the // call will actually be synchronous. diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs index 57f180f8eccd56..7858683c6fe25e 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/DualModeSocketTest.cs @@ -880,9 +880,11 @@ protected DualModeConnectionlessSendToBase(ITestOutputHelper output) : base(outp [Fact] public async Task Socket_SendToV4IPEndPointToV4Host_Throws() { + // "The supplied EndPoint of AddressFamily InterNetwork is not valid for this Socket, use InterNetworkV6 instead." + // https://github.com/dotnet/runtime/issues/47772 using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp); socket.DualMode = false; - await Assert.ThrowsAsync( + await Assert.ThrowsAsync( () => SendToAsync(socket, new byte[1], new IPEndPoint(IPAddress.Loopback, DualModeBase.UnusedPort))); } diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs index 116053d971e989..930cebf2e80674 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs @@ -74,6 +74,21 @@ public async Task NullSocketAddress_Throws_ArgumentException() await AssertThrowsSynchronously(() => socket.SendToAsync(new byte[1], SocketFlags.None, socketAddress).AsTask()); } + // https://github.com/dotnet/runtime/issues/47772 + // SendTo should validate the remote end point's address family up front (like ReceiveFrom does) + // instead of failing deep inside the OS PAL with a SocketError.InvalidArgument. + [Fact] + public async Task AddressFamilyDoesNotMatch_Throws_ArgumentException() + { + using var ipv4Socket = CreateSocket(); + EndPoint ipV6Endpoint = GetGetDummyTestEndpoint(AddressFamily.InterNetworkV6); + await AssertThrowsSynchronously(() => SendToAsync(ipv4Socket, new ArraySegment(new byte[1]), ipV6Endpoint)); + + // The synchronous byte[] and span overloads must behave consistently as well. + Assert.Throws(() => ipv4Socket.SendTo(new byte[1], ipV6Endpoint)); + Assert.Throws(() => ipv4Socket.SendTo(new byte[1].AsSpan(), SocketFlags.None, ipV6Endpoint)); + } + [Fact] public async Task Datagram_UDP_ShouldImplicitlyBindLocalEndpoint() {