Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 8f8b04a

Browse files
authored
Add basic Socket.IOControl implementation on Unix (#25579)
Socket.IOControl uses Windows-specific code values, both in terms of the actual code numbers and in terms of lots of them being Windows-specific functionality. But a few of the codes do translate across OSes in terms of functionality, and we've heard from at least a few developers who were using these codes and expected them to work on Unix.
1 parent 97b535f commit 8f8b04a

File tree

5 files changed

+174
-11
lines changed

5 files changed

+174
-11
lines changed

src/Common/src/Interop/Unix/System.Native/Interop.GetBytesAvailable.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ internal static partial class Sys
1111
{
1212
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetBytesAvailable")]
1313
internal static extern unsafe Error GetBytesAvailable(SafeHandle socket, int* available);
14+
15+
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetAtOutOfBandMark")]
16+
internal static extern unsafe Error GetAtOutOfBandMark(SafeHandle socket, int* available);
1417
}
1518
}

src/Native/Unix/System.Native/pal_networking.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2272,7 +2272,7 @@ int32_t SystemNative_Socket(int32_t addressFamily, int32_t socketType, int32_t p
22722272
return *createdSocket != -1 ? Error_SUCCESS : SystemNative_ConvertErrorPlatformToPal(errno);
22732273
}
22742274

2275-
int32_t SystemNative_GetBytesAvailable(intptr_t socket, int32_t* available)
2275+
int32_t GetInt32Ioctl(intptr_t socket, unsigned long request, int32_t* available)
22762276
{
22772277
if (available == NULL)
22782278
{
@@ -2283,7 +2283,7 @@ int32_t SystemNative_GetBytesAvailable(intptr_t socket, int32_t* available)
22832283

22842284
int avail;
22852285
int err;
2286-
while ((err = ioctl(fd, FIONREAD, &avail)) < 0 && errno == EINTR);
2286+
while ((err = ioctl(fd, request, &avail)) < 0 && errno == EINTR);
22872287
if (err == -1)
22882288
{
22892289
return SystemNative_ConvertErrorPlatformToPal(errno);
@@ -2293,6 +2293,16 @@ int32_t SystemNative_GetBytesAvailable(intptr_t socket, int32_t* available)
22932293
return Error_SUCCESS;
22942294
}
22952295

2296+
int32_t SystemNative_GetAtOutOfBandMark(intptr_t socket, int32_t* available)
2297+
{
2298+
return GetInt32Ioctl(socket, SIOCATMARK, available);
2299+
}
2300+
2301+
int32_t SystemNative_GetBytesAvailable(intptr_t socket, int32_t* available)
2302+
{
2303+
return GetInt32Ioctl(socket, FIONREAD, available);
2304+
}
2305+
22962306
#if HAVE_EPOLL
22972307

22982308
static const size_t SocketEventBufferElementSize = sizeof(struct epoll_event) > sizeof(struct SocketEvent) ? sizeof(struct epoll_event) : sizeof(struct SocketEvent);

src/Native/Unix/System.Native/pal_networking.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ int32_t SystemNative_SetSockOpt(
386386

387387
int32_t SystemNative_Socket(int32_t addressFamily, int32_t socketType, int32_t protocolType, intptr_t* createdSocket);
388388

389+
int32_t SystemNative_GetAtOutOfBandMark(intptr_t socket, int32_t* available);
390+
389391
int32_t SystemNative_GetBytesAvailable(intptr_t socket, int32_t* available);
390392

391393
int32_t SystemNative_CreateSocketEventPort(intptr_t* port);

src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,15 @@ public static unsafe SocketError GetAvailable(SafeCloseSocket handle, out int av
816816
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
817817
}
818818

819+
public static unsafe SocketError GetAtOutOfBandMark(SafeCloseSocket handle, out int atOutOfBandMark)
820+
{
821+
int value = 0;
822+
Interop.Error err = Interop.Sys.GetAtOutOfBandMark(handle, &value);
823+
atOutOfBandMark = value;
824+
825+
return err == Interop.Error.SUCCESS ? SocketError.Success : GetSocketErrorForErrorCode(err);
826+
}
827+
819828
public static unsafe SocketError GetPeerName(SafeCloseSocket handle, byte[] buffer, ref int nameLen)
820829
{
821830
Interop.Error err;
@@ -1029,8 +1038,45 @@ public static SocketError ReceiveFrom(SafeCloseSocket handle, byte[] buffer, int
10291038
}
10301039

10311040
public static SocketError WindowsIoctl(SafeCloseSocket handle, int ioControlCode, byte[] optionInValue, byte[] optionOutValue, out int optionLength)
1032-
{
1033-
throw new PlatformNotSupportedException(SR.PlatformNotSupported_IOControl);
1041+
{
1042+
// Three codes are called out in the Winsock IOCTLs documentation as "The following Unix IOCTL codes (commands) are supported." They are
1043+
// also the three codes available for use with ioctlsocket on Windows. Developers should be discouraged from using Socket.IOControl in
1044+
// cross -platform applications, as it accepts Windows-specific values (the value of FIONREAD is different on different platforms), but
1045+
// we make a best-effort attempt to at least keep these codes behaving as on Windows.
1046+
const int FIONBIO = unchecked((int)IOControlCode.NonBlockingIO);
1047+
const int FIONREAD = (int)IOControlCode.DataToRead;
1048+
const int SIOCATMARK = (int)IOControlCode.OobDataRead;
1049+
1050+
optionLength = 0;
1051+
switch (ioControlCode)
1052+
{
1053+
case FIONBIO:
1054+
// The Windows implementation explicitly throws this exception, so that all
1055+
// changes to blocking/non-blocking are done via Socket.Blocking.
1056+
throw new InvalidOperationException(SR.net_sockets_useblocking);
1057+
1058+
case FIONREAD:
1059+
case SIOCATMARK:
1060+
if (optionOutValue == null || optionOutValue.Length < sizeof(int))
1061+
{
1062+
return SocketError.Fault;
1063+
}
1064+
1065+
int result;
1066+
SocketError error = ioControlCode == FIONREAD ?
1067+
GetAvailable(handle, out result) :
1068+
GetAtOutOfBandMark(handle, out result);
1069+
if (error == SocketError.Success)
1070+
{
1071+
optionLength = sizeof(int);
1072+
BitConverter.TryWriteBytes(optionOutValue, result);
1073+
}
1074+
return error;
1075+
1076+
default:
1077+
// Every other control code is unknown to us for and is considered unsupported on Unix.
1078+
throw new PlatformNotSupportedException(SR.PlatformNotSupported_IOControl);
1079+
}
10341080
}
10351081

10361082
private static SocketError GetErrorAndTrackSetting(SafeCloseSocket handle, SocketOptionLevel optionLevel, SocketOptionName optionName, Interop.Error err)

src/System.Net.Sockets/tests/FunctionalTests/OSSupport.cs

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55

6+
using System.Threading;
67
using Xunit;
78

89
namespace System.Net.Sockets.Tests
@@ -42,20 +43,121 @@ public void UseOnlyOverlappedIO_AlwaysFalse()
4243
}
4344
}
4445

45-
[PlatformSpecific(TestPlatforms.Windows)] // Windows IOCTL
4646
[Fact]
4747
public void IOControl_FIONREAD_Success()
4848
{
4949
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
5050
{
51-
byte[] outValue = new byte[sizeof(int)];
51+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.DataToRead, null, null));
52+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.DataToRead, null, new byte[0]));
53+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.DataToRead, null, new byte[sizeof(int) - 1]));
5254

53-
const int FIONREAD = 0x4004667F;
54-
Assert.Equal(4, client.IOControl(FIONREAD, null, outValue));
55-
Assert.Equal(client.Available, BitConverter.ToInt32(outValue, 0));
55+
byte[] fionreadResult = new byte[sizeof(int)];
5656

57-
Assert.Equal(4, client.IOControl(IOControlCode.DataToRead, null, outValue));
58-
Assert.Equal(client.Available, BitConverter.ToInt32(outValue, 0));
57+
Assert.Equal(4, client.IOControl(IOControlCode.DataToRead, null, fionreadResult));
58+
Assert.Equal(client.Available, BitConverter.ToInt32(fionreadResult, 0));
59+
Assert.Equal(0, BitConverter.ToInt32(fionreadResult, 0));
60+
61+
Assert.Equal(4, client.IOControl((int)IOControlCode.DataToRead, null, fionreadResult));
62+
Assert.Equal(client.Available, BitConverter.ToInt32(fionreadResult, 0));
63+
Assert.Equal(0, BitConverter.ToInt32(fionreadResult, 0));
64+
65+
using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
66+
{
67+
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
68+
listener.Listen(1);
69+
70+
client.Connect(listener.LocalEndPoint);
71+
using (Socket server = listener.Accept())
72+
{
73+
server.Send(new byte[] { 42 });
74+
Assert.True(SpinWait.SpinUntil(() => client.Available != 0, 10_000));
75+
76+
Assert.Equal(4, client.IOControl(IOControlCode.DataToRead, null, fionreadResult));
77+
Assert.Equal(client.Available, BitConverter.ToInt32(fionreadResult, 0));
78+
Assert.Equal(1, BitConverter.ToInt32(fionreadResult, 0));
79+
}
80+
}
81+
}
82+
}
83+
84+
[ActiveIssue(25639, TestPlatforms.OSX)]
85+
[Fact]
86+
public void IOControl_SIOCATMARK_Success()
87+
{
88+
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
89+
{
90+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.OobDataRead, null, null));
91+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.OobDataRead, null, new byte[0]));
92+
Assert.Throws<SocketException>(() => client.IOControl(IOControlCode.OobDataRead, null, new byte[sizeof(int) - 1]));
93+
94+
using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
95+
{
96+
listener.Bind(new IPEndPoint(IPAddress.Loopback, 0));
97+
listener.Listen(1);
98+
99+
client.Connect(listener.LocalEndPoint);
100+
using (Socket server = listener.Accept())
101+
{
102+
byte[] siocatmarkResult = new byte[sizeof(int)];
103+
104+
server.Send(new byte[] { 42 }, SocketFlags.None);
105+
server.Send(new byte[] { 43 }, SocketFlags.OutOfBand);
106+
107+
Assert.Equal(4, client.IOControl(IOControlCode.OobDataRead, null, siocatmarkResult));
108+
Assert.Equal(0, BitConverter.ToInt32(siocatmarkResult, 0));
109+
110+
var received = new byte[1];
111+
112+
Assert.Equal(1, client.Receive(received));
113+
Assert.Equal(42, received[0]);
114+
115+
Assert.Equal(1, client.Receive(received, SocketFlags.OutOfBand));
116+
Assert.Equal(43, received[0]);
117+
118+
Assert.True(SpinWait.SpinUntil(() =>
119+
{
120+
Assert.Equal(4, client.IOControl(IOControlCode.OobDataRead, null, siocatmarkResult));
121+
return BitConverter.ToInt32(siocatmarkResult, 0) == 1;
122+
}, 10_000));
123+
}
124+
}
125+
}
126+
}
127+
128+
[Fact]
129+
public void IOControl_FIONBIO_Throws()
130+
{
131+
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
132+
{
133+
Assert.Throws<InvalidOperationException>(() => client.IOControl(unchecked((int)IOControlCode.NonBlockingIO), null, null));
134+
Assert.Throws<InvalidOperationException>(() => client.IOControl(IOControlCode.NonBlockingIO, null, null));
135+
}
136+
}
137+
138+
[PlatformSpecific(TestPlatforms.AnyUnix)]
139+
[Fact]
140+
public void IOControl_UnknownValues_Unix_Throws()
141+
{
142+
using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
143+
{
144+
foreach (IOControlCode code in Enum.GetValues(typeof(IOControlCode)))
145+
{
146+
switch (code)
147+
{
148+
case IOControlCode.NonBlockingIO:
149+
case IOControlCode.DataToRead:
150+
case IOControlCode.OobDataRead:
151+
// These three codes are currently enabled on Unix.
152+
break;
153+
154+
default:
155+
// The rest should throw PNSE.
156+
Assert.Throws<PlatformNotSupportedException>(() => client.IOControl((int)code, null, null));
157+
Assert.Throws<PlatformNotSupportedException>(() => client.IOControl(code, null, null));
158+
break;
159+
}
160+
}
59161
}
60162
}
61163
}

0 commit comments

Comments
 (0)