Search code examples
c#asp.net-core.net-7.0kestrel-http-serverunix-socket

.net 7.0 kestrel appsettings.json configuration for unix sockets


I have an open-source service application written in c#, running on .net 7, on Linux, which exposes an HTTP service, using Kestrel, to provide some monitoring endpoints for application state and important metrics for monitoring the application. It is configured to listen on a TCP endpoint, in appsettings.json, and that's great for when that's the desired configuration.

I also want to have Kestrel be able to listen on unix sockets, for reverse-proxy setups, among other reasons, at a specific path.

I can do it in code, fairly easily, but I would prefer to be able to configure it using the Kestrel section of my appsettings.json, as is done for the TCP socket listener, so end-users of the application can use standard configuration, rather than settings specific to just this application, that then get consumed in code to make kestrel listen at the desired socket path.

Documentation on how to do that seems to be non-existent, after many hours of scouring the net, including SO, Microsoft Learn, and numerous blogs.

Does anybody know how to configure Kestrel to listen on unix sockets at a specific socket path using appsettings.json?

Here's how the configuration is being given to Kestrel:

public static Task Main(string[] args)
{
    // ...
    WebApplicationBuilder serviceBuilder = WebApplication.CreateBuilder( );
    // ...
    serviceBuilder.WebHost
                  .UseKestrel( ConfigureKestrelOptions );
    // ...
}
      
private static void ConfigureKestrelOptions( WebHostBuilderContext builderContext, KestrelServerOptions kestrelOptions )
{
    kestrelOptions.Configure(_configurationRoot!.GetRequiredSection("Monitoring").GetSection("Kestrel") )
                  .Load( );
}

One attempted configuration was this:

{
  "Monitoring": {
    "Kestrel": {
      "AllowedHosts": "*",
      "Endpoints": {
        "HttpMonitoringEndpoint": {
          "Url": "http://*:60765"
        },
        "UnixSocketMonitoringEndpoint": {
          "SocketPath": "/run/myapp/monitor.sock"
        } 
      }
    }
  }
}

I have also tried putting the unix socket endpoint in a "UnixSocketEndpoints" and "UnixDomainSocketEndpoints" section, instead of "Endpoints." Those result in a unix socket being opened, but at a random temporary path, rather than the path specified.


Solution

  • After going over the current source code for .net 7.0.9, in github, following every function call during the loading of configuration, I figured out what needed to be done.

    The relevant source files for kestrel configuration are these:

    First, there's the KestrelServerOptions class, which Kestrel uses, both for configuration in code and from ConfigurationProviders that can provide IConfigurationSections to Kestrel.

    Following the code around eventually leads you to realizing that the "Endpoints" collection in the JSON is made up of instances of the EndpointConfiguration class, which both represents what can be defined in configuration for items in the "Endpoints" section, as well as what can be done in code, for an abstract "Endpoint."

    All Endpoints have a ListenOptions property, which defines the specific settings each EndPoint entry can have, such as the familiar options Url and Protocols, among others. That property is of a type with the same name, located in ListenOptions.cs.

    If you combine all of that and apply how those classes are structured to the configuration, you end up with something like this (TCP endpoint also included to show similarity):

    {
      "Endpoints": {
        "HttpMonitoringEndpoint": {
          "Url": "http://*:60763"
        },
        "UnixSocketMonitoringEndpoint": {
          "Url": "http://unix:/path/to/my.sock"
        }
      }
    }
    

    This works, and creates a unix socket at the specified Url.

    This can, of course, be placed in any part of your appsettings.json (or other json file you load), so long as the IConfigurationSection you give to Kestrel, in the call to Configure() is that specific section.

    Notes:

    The .net code for the KestrelConfigurationLoader will ignore anything between the unix: and the first slash. If the socket path is not rooted, you'll get an exception.

    You must also make sure the directory the socket file is going to be placed in already exists, or you'll get this exception:

    Unhandled exception. System.Net.Sockets.SocketException (99): Cannot assign requested address
    

    Key is to note that 99 is an errno value as defined in libc, which is EADDRNOTAVAIL.

    .net will not create any directories for you, for this, so just be sure the directory you're placing the socket file in already exists and that the account/group your process is executing as has appropriate permissions to the directory.