File: Interop\NativeTls.cs
Web Access
Project: src\aspnetcore\src\Servers\Kestrel\Transport.DirectTls\src\Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls.csproj (Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Server.Kestrel.Transport.DirectTls.Interop;
 
internal static partial class NativeTls
{
    private const string LIBC = "libc";
 
    // The native epoll_event layout differs by architecture: x86_64/i386 pack the struct (12 bytes, data at
    // offset 4) while every other architecture aligns it naturally (16 bytes, data at offset 8). A single
    // managed [StructLayout] cannot satisfy both, and one assembly is loaded on every architecture, so the
    // matching native struct (EpollEventPacked or EpollEventAligned) is selected at runtime.
    private static readonly bool s_packedEpoll =
        RuntimeInformation.ProcessArchitecture is Architecture.X64 or Architecture.X86;
 
    // Exposed for tests: true when this architecture uses the 12-byte packed epoll_event layout.
    internal static bool UsesPackedEpollLayout => s_packedEpoll;
 
    // Epoll. SetLastError = true is required so Marshal.GetLastPInvokeError() returns the real errno
    // after a failed call; [LibraryImport] defaults to SetLastError = false, which would otherwise leave
    // callers (notably the EINTR retry in TlsEventPump.PumpLoop) reading a stale/garbage value.
    [LibraryImport(LIBC, SetLastError = true)] public static partial int epoll_create1(int flags);
    [LibraryImport(LIBC, SetLastError = true)] public static partial int epoll_ctl(int epfd, int op, int fd, IntPtr ev);
    [LibraryImport(LIBC, SetLastError = true)] public static partial int close(int fd);
 
    // accept4: accept a pending connection and return a new fd, or -1 with errno set. addr/addrlen are passed
    // as IntPtr.Zero (the peer is read later via the wrapped managed Socket's RemoteEndPoint, so no sockaddr is
    // parsed here). SOCK_NONBLOCK sets the accepted fd non-blocking atomically and SOCK_CLOEXEC sets close-on-exec
    // atomically (so the fd is not inherited across exec), both saving a follow-up fcntl.
    // Reporting EAGAIN/EBADF/EINTR as errno return values lets the accept loop drain without exceptions.
    [LibraryImport(LIBC, SetLastError = true)] public static partial int accept4(int sockfd, IntPtr addr, IntPtr addrlen, int flags);
 
    [LibraryImport(LIBC, SetLastError = true)]
    private static partial int epoll_ctl(int epfd, int op, int fd, ref EpollEventPacked ev);
 
    [LibraryImport(LIBC, SetLastError = true)]
    private static partial int epoll_ctl(int epfd, int op, int fd, ref EpollEventAligned ev);
 
    [LibraryImport(LIBC, SetLastError = true)]
    private static partial int epoll_wait(int epfd, EpollEventPacked[] events, int maxevents, int timeout);
 
    [LibraryImport(LIBC, SetLastError = true)]
    private static partial int epoll_wait(int epfd, EpollEventAligned[] events, int maxevents, int timeout);
 
    // Registers/modifies interest using the architecture-correct native epoll_event struct.
    public static int epoll_ctl(int epfd, int op, int fd, ref EpollEvent ev)
    {
        if (s_packedEpoll)
        {
            var native = new EpollEventPacked { Events = ev.Events, Data = ev.Data.U64 };
            return epoll_ctl(epfd, op, fd, ref native);
        }
        else
        {
            var native = new EpollEventAligned { Events = ev.Events, Data = ev.Data.U64 };
            return epoll_ctl(epfd, op, fd, ref native);
        }
    }
 
    // Reusable epoll_wait result buffer that holds whichever native array matches the architecture ABI and
    // exposes entries as the logical EpollEvent, keeping the pump loop free of any per-entry layout branch.
    internal sealed class EpollEventBuffer
    {
        private readonly EpollEventPacked[]? _packed;
        private readonly EpollEventAligned[]? _aligned;
 
        public EpollEventBuffer(int maxEvents)
        {
            if (s_packedEpoll)
            {
                _packed = new EpollEventPacked[maxEvents];
            }
            else
            {
                _aligned = new EpollEventAligned[maxEvents];
            }
        }
 
        public int Wait(int epfd, int timeout)
            => _packed is not null
                ? epoll_wait(epfd, _packed, _packed.Length, timeout)
                : epoll_wait(epfd, _aligned!, _aligned!.Length, timeout);
 
        public EpollEvent this[int index]
            => _packed is not null
                ? new EpollEvent { Events = _packed[index].Events, Data = new EpollData { U64 = _packed[index].Data } }
                : new EpollEvent { Events = _aligned![index].Events, Data = new EpollData { U64 = _aligned[index].Data } };
    }
 
    // Epoll constants
    public const int EPOLL_CTL_ADD = 1;
    public const int EPOLL_CTL_DEL = 2;
    public const int EPOLL_CTL_MOD = 3;
    public const uint EPOLLIN = 0x001;
    public const uint EPOLLOUT = 0x004;
    public const uint EPOLLERR = 0x008;
    public const uint EPOLLHUP = 0x010;
    public const uint EPOLLET = 0x80000000;
    public const uint EPOLLRDHUP = 0x2000;
    public const uint EPOLLEXCLUSIVE = 0x10000000;  // Prevents thundering herd - only one worker wakes per event
 
    // accept4 / epoll_create1 flags. Both CLOEXEC values equal Linux O_CLOEXEC (0x80000) on the architectures
    // this transport targets (x64/arm64/arm). SOCK_NONBLOCK sets the accepted fd non-blocking atomically;
    // SOCK_CLOEXEC / EPOLL_CLOEXEC set close-on-exec atomically so accepted connection fds and the epoll fd are
    // not inherited by a child process across exec (matching the runtime's own socket layer).
    public const int SOCK_NONBLOCK = 0x800;    // Linux O_NONBLOCK
    public const int SOCK_CLOEXEC = 0x80000;   // Linux O_CLOEXEC
    public const int EPOLL_CLOEXEC = 0x80000;  // Linux O_CLOEXEC
 
    // errno values the accept loop distinguishes (Linux asm-generic/errno-base.h). EWOULDBLOCK == EAGAIN on Linux.
    public const int EINTR = 4;    // interrupted by a signal before a connection was accepted - retry
    public const int EBADF = 9;    // listen fd closed underneath the pump during shutdown
    public const int EAGAIN = 11;  // backlog drained (non-blocking accept has nothing pending)
}