Search code examples
asp.net-corekestrel-http-serverwindows-firewall

Kestrel created in Console app refuses connections


This is a continuation of this issue.

Find 2 different FREE ports for 2 kestrel servers

I was able to start 2 kestrel servers from Console app using dynamic ports and on some computers it works fine, but sometimes this approach fails.

TLDR

  1. Kestrel should create HTTP and HTTPS connection, but creates only HTTP
  2. Even when it says that HTTP connection was created it's not reachable

For testing purposes, I created Firewall rule that opens all ports for all protocols and all apps, and then completely disabled Firewall itself. For clarity, I'm not using IIS and start Kestrel manually from C# Console application as local server.

Issues

  1. Code in the link above should create HTTPS connection for each server, but on Windows Server 2012 and some Windows 10 Pro computers it creates only HTTP connection, although having HTTPS is not critical in this case if I knew how to solve another problem below. I think, it's because some update in Windows may require all HTTPS connections to have SSL certificate and as far as I don't provide it, server doesn't even try to create HTTPS connection. Is there a settings in Kestrel to ignore SSL certificate validation for localhost?
public static IWebHost Run<T>(ServerOptions options = null) where T : class
{
  var configuration = new ConfigurationBuilder().Build();

  var urls = new[]
  {
    "http://0.0.0.0:0",
    "https://0.0.0.0:0"
  };

  var environment = WebHost
    .CreateDefaultBuilder(new string[0])
    .ConfigureServices(o => o.AddSingleton(options))
    .UseConfiguration(configuration)
    .UseUrls(urls)
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseKestrel()
    //.UseIISIntegration()
    .UseStartup<T>()
    .Build();

  environment.RunAsync();

  return environment;
}

// Issue #1 
// On some computers 
// webAddresses = ["http://0.0.0.0:50210", "https://0.0.0.0:50220"]
// On other computers and even for 2 users on the same Windows server it doesn't create HTTPS 
// webAddresses = ["http://0.0.0.0:50210"]

var webEnvironment = Server.Run<Startup>();
var webAddresses = webEnvironment.ServerFeatures.Get<IServerAddressesFeature>().Addresses; 
  1. Second issue is that on some computers the code gets executed without any error or exception and returns dynamic address for the server, e.g. http://0.0.0.0:50210 but when I try address http://127.0.0.1:50210 in a browser, it says that website cannot be reached and connection to this server was actively refused. I would blame some incompatibility issues with Windows Server 2012 and Windows 8 and Kestrel, but I have Windows 10 VPS where this app works fine for one system user and cannot create HTTPS for another one and the only difference between those users is that I modified NETSH rules for one of them. I could probably create BAT script trying to open all ports for all users and that would allow my app with dynamic ports to function as expected, but maybe there is some Kestrel settings that would make application available on dynamic port without raping Firewall and network settings?

Solution

  • Hacky solution

    There is an actual incompatibility between Windows Server 2012 and Kestrel. To make them compatible, I had to enforce Kestrel to use HTTP/1 protocol instead of HTTP/2 set by default.

    https://stackoverflow.com/a/59756935/437393

    Final Version for ASP Core 3.1 and Windows Server 2012

    public static IWebHost Run<T>(ServerOptions options = null) where T : class
    {
      //var urls = new[]
      //{
      //  "http://127.0.0.1:0",
      //  "https://127.0.0.1:0"
      //};
    
      var configuration = new ConfigurationBuilder().Build();
      var environment = WebHost
        .CreateDefaultBuilder(new string[0])
        .ConfigureServices(o => o.AddSingleton(options))
        .UseConfiguration(configuration)
        //.UseUrls(urls)
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseKestrel(o =>
        {
          o.Listen(IPAddress.Loopback, 0, v => v.Protocols = HttpProtocols.Http1); // FIX!!!
          //o.Listen(IPAddress.Loopback, 0, v =>
          //{
          //  v.UseHttps();
          //  v.Protocols = HttpProtocols.Http1;
          //});
        })
        .UseStartup<T>()
        .Build();
    
      environment.RunAsync();
    
      return environment;
    }
    

    Issue with inability to establish HTTPS connection is still open, because when I use hacky solution above and try to create HTTPS instead of HTTP I get an exception

    Cannot access a disposed object. Object name: 'IServiceProvider'
    

    In this line

    var serviceEnvironment = Server.Run<ServiceStartup>();
    var webEnvironment = Server.Run<WebStartup>(); // Exception happens here
    
    

    Maybe, because Kestrel is trying to reuse existing connection from the first server...

    Update

    As far as I can create only one connection, decided to always use HTTPS with auto-generated certificate for localhost and disabled SSL verification in CefSharp.

    public static IWebHost Run<T>(ServerOptions options = null) where T : class
    {
      var configuration = new ConfigurationBuilder().Build();
      var environment = WebHost
        .CreateDefaultBuilder(new string[0])
        .ConfigureServices(o => o.AddSingleton(options))
        .UseConfiguration(configuration)
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseKestrel(options =>
        {
          options.Listen(IPAddress.Loopback, 0, o =>
          {
            o.UseHttps("Certificate.pfx", "SomePassword");
            o.Protocols = HttpProtocols.Http1;
          });
        })
        .UseStartup<T>()
        .Build();
    
      environment.StartAsync();
    
      return environment;
    }
    
    // Disable errors about invalid local certificate in CefSharp
    
    var browserOptions = new BrowserSettings
    {
      WebSecurity = CefState.Disabled,
      FileAccessFromFileUrls = CefState.Enabled,
      UniversalAccessFromFileUrls = CefState.Enabled
    };
    
    Browser.BrowserSettings = browserOptions;