-
Notifications
You must be signed in to change notification settings - Fork 24
/
UDPSocketLinux.cs
177 lines (156 loc) · 5.85 KB
/
UDPSocketLinux.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
using System;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Runtime.InteropServices;
namespace GitHub.JPMikkers.DHCP;
public class UDPSocketLinux : IUDPSocket
{
// see https://github.com/dotnet/runtime/issues/83525
const int SO_BINDTODEVICE = 25;
const int SOL_SOCKET = 1;
private bool _disposed; // true => object is disposed
private readonly bool _IPv6; // true => it's an IPv6 connection
private readonly Socket _socket; // The active socket
private readonly int _maxPacketSize; // size of packets we'll try to receive
private readonly IPEndPoint _localEndPoint;
public IPEndPoint LocalEndPoint
{
get
{
return _localEndPoint;
}
}
public UDPSocketLinux(IPEndPoint localEndPoint, int maxPacketSize, bool dontFragment, short ttl)
{
foreach(var nic in NetworkInterface.GetAllNetworkInterfaces())
{
Console.WriteLine($"{nic.Id}");
}
var selectedNic = NetworkInterface.GetAllNetworkInterfaces()
.Where(x => x.GetIPProperties().UnicastAddresses.Select(a => a.Address).Contains(localEndPoint.Address))
.FirstOrDefault();
if(selectedNic is null)
{
throw new UDPSocketException($"Can't find the appropriate network interface associated with endpoint '{localEndPoint}'") { IsFatal = true };
}
_maxPacketSize = maxPacketSize;
_disposed = false;
_IPv6 = (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6);
_socket = new Socket(localEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
_socket.EnableBroadcast = true;
_socket.ExclusiveAddressUse = false; //_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
_socket.SendBufferSize = 65536;
_socket.ReceiveBufferSize = 65536;
if(!_IPv6) _socket.DontFragment = dontFragment;
if(ttl >= 0)
{
_socket.Ttl = ttl;
}
if(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_socket.SetRawSocketOption(SOL_SOCKET, SO_BINDTODEVICE, Encoding.UTF8.GetBytes(selectedNic.Id));
}
_socket.Bind(new IPEndPoint(IPAddress.Any, localEndPoint.Port));
_localEndPoint = localEndPoint;
}
public async Task<(IPEndPoint, ReadOnlyMemory<byte>)> Receive(CancellationToken cancellationToken)
{
try
{
var mem = new Memory<byte>(new byte[_maxPacketSize]);
var result = await _socket.ReceiveFromAsync(mem, new IPEndPoint(IPAddress.Any, 0), cancellationToken);
if(result.RemoteEndPoint is IPEndPoint endpoint)
{
return (endpoint, mem[..result.ReceivedBytes]);
}
else
{
throw new InvalidCastException("unexpected endpoint type");
}
}
catch(SocketException ex) when(ex.SocketErrorCode == SocketError.MessageSize)
{
// someone tried to send a message bigger than _maxPacketSize
// discard it, and start receiving the next packet
throw new UDPSocketException($"{nameof(Receive)} error: {ex.Message}", ex) { IsFatal = false };
}
catch(SocketException ex) when(ex.SocketErrorCode == SocketError.ConnectionReset)
{
// ConnectionReset is reported when the remote port wasn't listening.
// Since we're using UDP messaging we don't care about this -> continue receiving.
throw new UDPSocketException($"{nameof(Receive)} error: {ex.Message}", ex) { IsFatal = false };
}
catch(OperationCanceledException)
{
throw;
}
catch(Exception ex)
{
// everything else is fatal
throw new UDPSocketException($"{nameof(Receive)} error: {ex.Message}", ex) { IsFatal = true };
}
}
/// <summary>
/// Sends a packet of bytes to the specified EndPoint using an UDP datagram.
/// </summary>
/// <param name="endPoint">Target for the data</param>
/// <param name="msg">Data to send</param>
public async Task Send(IPEndPoint endPoint, ReadOnlyMemory<byte> msg, CancellationToken cancellationToken)
{
try
{
await _socket.SendToAsync(msg, endPoint, cancellationToken);
}
catch(OperationCanceledException)
{
throw;
}
catch(Exception ex)
{
throw new UDPSocketException($"{nameof(Send)}", ex) { IsFatal = true };
}
}
~UDPSocketLinux()
{
try
{
Dispose(false);
}
catch
{
// never let any exception escape the finalizer, or else your process will be killed.
}
}
/// <summary>
/// Implements <see cref="IDisposable.Dispose"/>
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(disposing)
{
if(!_disposed)
{
_disposed = true;
try
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
catch(Exception)
{
// socket tends to complain a lot during close. just eat those exceptions.
}
}
}
}
}