199 lines
7.1 KiB
C#
199 lines
7.1 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Diagnostics;
|
|
using System.IO.Pipelines;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.ExceptionServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Protocols;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
|
|
using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.Internal;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets
|
|
{
|
|
internal sealed class SocketTransport : ITransport
|
|
{
|
|
private readonly MemoryPool _memoryPool = new MemoryPool();
|
|
private readonly IEndPointInformation _endPointInformation;
|
|
private readonly IConnectionHandler _handler;
|
|
private readonly IApplicationLifetime _appLifetime;
|
|
private readonly ISocketsTrace _trace;
|
|
private Socket _listenSocket;
|
|
private Task _listenTask;
|
|
private Exception _listenException;
|
|
private volatile bool _unbinding;
|
|
|
|
internal SocketTransport(
|
|
IEndPointInformation endPointInformation,
|
|
IConnectionHandler handler,
|
|
IApplicationLifetime applicationLifetime,
|
|
ISocketsTrace trace)
|
|
{
|
|
Debug.Assert(endPointInformation != null);
|
|
Debug.Assert(endPointInformation.Type == ListenType.IPEndPoint);
|
|
Debug.Assert(handler != null);
|
|
Debug.Assert(applicationLifetime != null);
|
|
Debug.Assert(trace != null);
|
|
|
|
_endPointInformation = endPointInformation;
|
|
_handler = handler;
|
|
_appLifetime = applicationLifetime;
|
|
_trace = trace;
|
|
}
|
|
|
|
public Task BindAsync()
|
|
{
|
|
if (_listenSocket != null)
|
|
{
|
|
throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound);
|
|
}
|
|
|
|
IPEndPoint endPoint = _endPointInformation.IPEndPoint;
|
|
|
|
var listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
|
|
EnableRebinding(listenSocket);
|
|
|
|
// Kestrel expects IPv6Any to bind to both IPv6 and IPv4
|
|
if (endPoint.Address == IPAddress.IPv6Any)
|
|
{
|
|
listenSocket.DualMode = true;
|
|
}
|
|
|
|
try
|
|
{
|
|
listenSocket.Bind(endPoint);
|
|
}
|
|
catch (SocketException e) when (e.SocketErrorCode == SocketError.AddressAlreadyInUse)
|
|
{
|
|
throw new AddressInUseException(e.Message, e);
|
|
}
|
|
|
|
// If requested port was "0", replace with assigned dynamic port.
|
|
if (_endPointInformation.IPEndPoint.Port == 0)
|
|
{
|
|
_endPointInformation.IPEndPoint = (IPEndPoint)listenSocket.LocalEndPoint;
|
|
}
|
|
|
|
listenSocket.Listen(512);
|
|
|
|
_listenSocket = listenSocket;
|
|
|
|
_listenTask = Task.Run(() => RunAcceptLoopAsync());
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public async Task UnbindAsync()
|
|
{
|
|
if (_listenSocket != null)
|
|
{
|
|
_unbinding = true;
|
|
_listenSocket.Dispose();
|
|
|
|
Debug.Assert(_listenTask != null);
|
|
await _listenTask.ConfigureAwait(false);
|
|
|
|
_unbinding = false;
|
|
_listenSocket = null;
|
|
_listenTask = null;
|
|
|
|
if (_listenException != null)
|
|
{
|
|
var exInfo = ExceptionDispatchInfo.Capture(_listenException);
|
|
_listenException = null;
|
|
exInfo.Throw();
|
|
}
|
|
}
|
|
}
|
|
|
|
public Task StopAsync()
|
|
{
|
|
_memoryPool.Dispose();
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task RunAcceptLoopAsync()
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
var acceptSocket = await _listenSocket.AcceptAsync();
|
|
acceptSocket.NoDelay = _endPointInformation.NoDelay;
|
|
|
|
var connection = new SocketConnection(acceptSocket, _memoryPool, _trace);
|
|
_ = connection.StartAsync(_handler);
|
|
}
|
|
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
|
|
{
|
|
// REVIEW: Should there be a seperate log message for a connection reset this early?
|
|
_trace.ConnectionReset(connectionId: "(null)");
|
|
}
|
|
catch (SocketException ex) when (!_unbinding)
|
|
{
|
|
_trace.ConnectionError(connectionId: "(null)", ex);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (_unbinding)
|
|
{
|
|
// Means we must be unbinding. Eat the exception.
|
|
}
|
|
else
|
|
{
|
|
_trace.LogCritical(ex, $"Unexpected exeption in {nameof(SocketTransport)}.{nameof(RunAcceptLoopAsync)}.");
|
|
_listenException = ex;
|
|
|
|
// Request shutdown so we can rethrow this exception
|
|
// in Stop which should be observable.
|
|
_appLifetime.StopApplication();
|
|
}
|
|
}
|
|
}
|
|
|
|
[DllImport("libc", SetLastError = true)]
|
|
private static extern int setsockopt(int socket, int level, int option_name, IntPtr option_value, uint option_len);
|
|
|
|
private const int SOL_SOCKET_OSX = 0xffff;
|
|
private const int SO_REUSEADDR_OSX = 0x0004;
|
|
private const int SOL_SOCKET_LINUX = 0x0001;
|
|
private const int SO_REUSEADDR_LINUX = 0x0002;
|
|
|
|
// Without setting SO_REUSEADDR on macOS and Linux, binding to a recently used endpoint can fail.
|
|
// https://github.com/dotnet/corefx/issues/24562
|
|
private unsafe void EnableRebinding(Socket listenSocket)
|
|
{
|
|
var optionValue = 1;
|
|
var setsockoptStatus = 0;
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
setsockoptStatus = setsockopt(listenSocket.Handle.ToInt32(), SOL_SOCKET_LINUX, SO_REUSEADDR_LINUX,
|
|
(IntPtr)(&optionValue), sizeof(int));
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
setsockoptStatus = setsockopt(listenSocket.Handle.ToInt32(), SOL_SOCKET_OSX, SO_REUSEADDR_OSX,
|
|
(IntPtr)(&optionValue), sizeof(int));
|
|
}
|
|
|
|
if (setsockoptStatus != 0)
|
|
{
|
|
_trace.LogInformation("Setting SO_REUSEADDR failed with errno '{errno}'.", Marshal.GetLastWin32Error());
|
|
}
|
|
}
|
|
}
|
|
}
|