| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | using System.Buffers; |
| | 4 | | using System.Net; |
| | 5 | | using System.Net.Security; |
| | 6 | | using System.Net.Sockets; |
| | 7 | |
|
| | 8 | | namespace IceRpc.Transports.Tcp.Internal; |
| | 9 | |
|
| | 10 | | /// <summary>The listener implementation for the TCP transport.</summary> |
| | 11 | | internal sealed class TcpListener : IListener<IDuplexConnection> |
| | 12 | | { |
| 254 | 13 | | public ServerAddress ServerAddress { get; } |
| | 14 | |
|
| | 15 | | private readonly SslServerAuthenticationOptions? _authenticationOptions; |
| | 16 | | private readonly int _minSegmentSize; |
| | 17 | | private readonly MemoryPool<byte> _pool; |
| | 18 | | private readonly Socket _socket; |
| | 19 | |
|
| | 20 | | // Set to 1 when the listener is disposed. |
| | 21 | | private volatile int _disposed; |
| | 22 | |
|
| | 23 | | public async Task<(IDuplexConnection, EndPoint)> AcceptAsync(CancellationToken cancellationToken) |
| 220 | 24 | | { |
| | 25 | | try |
| 220 | 26 | | { |
| 220 | 27 | | Socket acceptedSocket = await _socket.AcceptAsync(cancellationToken).ConfigureAwait(false); |
| | 28 | |
|
| 168 | 29 | | var tcpConnection = new TcpServerConnection( |
| 168 | 30 | | acceptedSocket, |
| 168 | 31 | | _authenticationOptions, |
| 168 | 32 | | _pool, |
| 168 | 33 | | _minSegmentSize); |
| 168 | 34 | | return (tcpConnection, acceptedSocket.RemoteEndPoint!); |
| | 35 | | } |
| 6 | 36 | | catch (SocketException exception) |
| 6 | 37 | | { |
| 6 | 38 | | if (exception.SocketErrorCode == SocketError.OperationAborted) |
| 6 | 39 | | { |
| 6 | 40 | | ObjectDisposedException.ThrowIf(_disposed == 1, this); |
| 0 | 41 | | } |
| 0 | 42 | | throw exception.ToIceRpcException(); |
| | 43 | | } |
| 168 | 44 | | } |
| | 45 | |
|
| | 46 | | public ValueTask DisposeAsync() |
| 376 | 47 | | { |
| 376 | 48 | | if (Interlocked.Exchange(ref _disposed, 1) == 0) |
| 230 | 49 | | { |
| 230 | 50 | | _socket.Dispose(); |
| 230 | 51 | | } |
| 376 | 52 | | return default; |
| 376 | 53 | | } |
| | 54 | |
|
| 242 | 55 | | internal TcpListener( |
| 242 | 56 | | ServerAddress serverAddress, |
| 242 | 57 | | DuplexConnectionOptions options, |
| 242 | 58 | | SslServerAuthenticationOptions? authenticationOptions, |
| 242 | 59 | | TcpServerTransportOptions tcpOptions) |
| 242 | 60 | | { |
| 242 | 61 | | if (!IPAddress.TryParse(serverAddress.Host, out IPAddress? ipAddress)) |
| 6 | 62 | | { |
| 6 | 63 | | throw new ArgumentException( |
| 6 | 64 | | $"Listening on the DNS name '{serverAddress.Host}' is not allowed; an IP address is required.", |
| 6 | 65 | | nameof(serverAddress)); |
| | 66 | | } |
| | 67 | |
|
| 236 | 68 | | _authenticationOptions = authenticationOptions?.Clone(); |
| 236 | 69 | | _minSegmentSize = options.MinSegmentSize; |
| 236 | 70 | | _pool = options.Pool; |
| | 71 | |
|
| 236 | 72 | | if (_authenticationOptions is not null && _authenticationOptions.ApplicationProtocols is null) |
| 80 | 73 | | { |
| | 74 | | // Set ApplicationProtocols to "ice" or "icerpc" in the common situation where the application does not |
| | 75 | | // specify any application protocol. This way, a connection request that carries an ALPN protocol ID can |
| | 76 | | // only succeed if this protocol ID is a match. |
| 80 | 77 | | _authenticationOptions.ApplicationProtocols = new List<SslApplicationProtocol> |
| 80 | 78 | | { |
| 80 | 79 | | new SslApplicationProtocol(serverAddress.Protocol.Name) |
| 80 | 80 | | }; |
| 80 | 81 | | } |
| | 82 | |
|
| 236 | 83 | | var address = new IPEndPoint(ipAddress, serverAddress.Port); |
| | 84 | |
|
| | 85 | | // When using IPv6 address family we use the socket constructor without AddressFamily parameter to ensure |
| | 86 | | // dual-mode socket are used in platforms that support them. |
| 236 | 87 | | _socket = ipAddress.AddressFamily == AddressFamily.InterNetwork ? |
| 236 | 88 | | new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp) : |
| 236 | 89 | | new Socket(SocketType.Stream, ProtocolType.Tcp); |
| | 90 | | try |
| 236 | 91 | | { |
| 236 | 92 | | _socket.ExclusiveAddressUse = true; |
| 236 | 93 | | _socket.Configure(tcpOptions); |
| 236 | 94 | | _socket.Bind(address); |
| 230 | 95 | | address = (IPEndPoint)_socket.LocalEndPoint!; |
| 230 | 96 | | _socket.Listen(tcpOptions.ListenBacklog); |
| 230 | 97 | | } |
| 6 | 98 | | catch (SocketException exception) |
| 6 | 99 | | { |
| 6 | 100 | | _socket.Dispose(); |
| 6 | 101 | | throw exception.ToIceRpcException(); |
| | 102 | | } |
| 0 | 103 | | catch |
| 0 | 104 | | { |
| 0 | 105 | | _socket.Dispose(); |
| 0 | 106 | | throw; |
| | 107 | | } |
| | 108 | |
|
| 230 | 109 | | ServerAddress = serverAddress with { Port = (ushort)address.Port }; |
| 230 | 110 | | } |
| | 111 | | } |