dotnet / MQTTnet

MQTTnet is a high performance .NET library for MQTT based communication. It provides a MQTT client and a MQTT server (broker). The implementation is based on the documentation from http://mqtt.org/.
MIT License
4.46k stars 1.06k forks source link

asp.net core 2.1 - ConfigureServices - How to build-up MQTT Server Options #400

Closed dealproc closed 6 years ago

dealproc commented 6 years ago

Considering this code:

            var mqttServerOptions = new MqttServerOptionsBuilder()
                .WithDefaultEndpoint()
                .WithDefaultEndpointPort(9876)
                .WithConnectionValidator((ctx) =>
                {
                    var mediator = serviceProvider.GetService<IMediator>();
                    if (await mediator.Send(new AuthenticateTerminalForMqttCommand(ctx.Username, ctx.Password)))
                    {
                        logger.LogInformation("Client {0} has connected.", ctx.ClientId);
                        ctx.ReturnCode = MqttConnectReturnCode.ConnectionAccepted;
                        return;
                    }

                    logger.LogInformation("Client {0} has failed to authenticate.", ctx.ClientId);
                    ctx.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
                })
                .Build();

We cannot pull anything into ConfigureServices() but the IServiceCollection object. So, with asp.net core 2.1, how do we provide a connection validator, among other items? When I look at the main Configure() method, although we can do app.UseMqttServer((mqttServer) => { ... custom code here ... });, it does not seem to let us assign a custom connection validator, as the property is marked as ReadOnly at this point.

Any suggestions/ideas for how to overcome this want/need would be great.

henkep commented 6 years ago

Hi I have the same question here, I setup the basics in the BuildWebHost and I specify a port there. But then I want to add certificates and custom callbacks and I can´t seam to figure out how to do that.

//Henrik

JanEggers commented 6 years ago

have a look at #402 you could copy that code as long as it is not merged.

I created a new OptionsBuilder that lets you access the service provider and resolve services as you desire:

        services.AddHostedMqttServer(builder => builder 
                .WithDefaultEndpoint()
                .WithDefaultEndpointPort(9876)
                .WithConnectionValidator((ctx) =>
                {
                    var mediator = builder.ServiceProvider.GetService<IMediator>();
                    if (await mediator.Send(new AuthenticateTerminalForMqttCommand(ctx.Username, ctx.Password)))
                    {
                        logger.LogInformation("Client {0} has connected.", ctx.ClientId);
                        ctx.ReturnCode = MqttConnectReturnCode.ConnectionAccepted;
                        return;
                    }

                    logger.LogInformation("Client {0} has failed to authenticate.", ctx.ClientId);
                    ctx.ReturnCode = MqttConnectReturnCode.ConnectionRefusedBadUsernameOrPassword;
                })
);
dealproc commented 6 years ago

That's pretty much what I wound up with. I now need to go through the bowels of the wire-up and see if its going to be possible or not to build an orleans backplane for this, so that we can host this within a web farm environment. I know others have advised against this, but I'm trying to keep our stack as compact as possible until we hit a critical mass.

henkep commented 6 years ago

Hi again

I must be stupid but I can´t get this to work with your changes. In my Program.cs file I have this: `private static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseKestrel(o => { o.ListenAnyIP(1883, l => l.UseMqtt()); o.ListenAnyIP(5000);

            })
            .UseStartup<Startup>().Build();`

And then in Startup.cs I implemented your changes after copying the new changes into my local MQTTnet project.

`public void ConfigureServices(IServiceCollection services) { services.AddHostedMqttServer(builder => builder .WithDefaultEndpoint() .WithDefaultEndpointPort(2222) );

        //this adds tcp server support based on Microsoft.AspNetCore.Connections.Abstractions
        services.AddMqttConnectionHandler();

        //this adds websocket support
        services.AddMqttWebSocketServerAdapter();

    }`

But the server still starts up on only 1883.

dealproc commented 6 years ago

Why are you asking it to do port 1833 and port 2222???

henkep commented 6 years ago

He he I realize that looks a bit strange. What I actually want to do is this: .WithConnectionBacklog(100) .WithConnectionValidator(ConnectionValidator) .WithSubscriptionInterceptor(SubscriptionValidator) .WithEncryptionCertificate(mainSubCert.Export(X509ContentType.SerializedCert)) .WithEncryptedEndpointPort(9000) .WithEncryptedEndpoint() .WithDefaultEndpointPort(0);

henkep commented 6 years ago

Has anybody managed to get a asp.net core broker started with certificates using this code. I just can´t figure out how it is done.. It keeps just starting a unencrypted broker with the port specified in my BuildWebHost method.

dealproc commented 6 years ago

If that's your pain point, maybe use something like Nginx on the front-end and then go between nginx and your service endpoint un-encrypted? Just a thought (and how we are presently configured)

henkep commented 6 years ago

I just can´t let this go.. Asp.NET Core refuses to start an encrypted endpoint for me. I am using the develop branch with the new ServiceCollectionExtensions.cs by JanEggers

Program.cs looks like this:

public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseKestrel(o =>
            {
                o.ListenAnyIP(9010, l => l.UseMqtt()); // mqtt pipeline
                o.ListenAnyIP(5000); // default http pipeline
            })
            .UseStartup<Startup>();`

Startup.cs looks like this: public void ConfigureServices(IServiceCollection services) { var rootFolder = Directory.GetCurrentDirectory(); rootFolder = rootFolder + "\device_test_com.pfx"; //2018 test certificate var appCert = new X509Certificate2(File.ReadAllBytes(rootFolder), "password", X509KeyStorageFlags.Exportable);

        var appSubCert = new X509Certificate2(appCert.Export(X509ContentType.Pfx));

        services.AddHostedMqttServer(builder => builder
            .WithEncryptionCertificate(appSubCert.Export(X509ContentType.SerializedCert))
            .WithEncryptedEndpointPort(9010)
            .WithEncryptedEndpoint()
            .WithConnectionValidator(Value)
            .WithoutDefaultEndpoint()
        );

        services.AddMqttConnectionHandler();

        services.AddMqttWebSocketServerAdapter();

    }

`

The server starts but it refuses connections with TLS on port 9010, unencrypted clients can connect though on port 9010 for some reason.

dealproc commented 6 years ago

Maybe use an nginx proxy with a tcp stream configuration? that's how I'm configured as of today.

henkep commented 6 years ago

I would love that config, but I can´t make it myself. If you would be willing to help me send me an email to henrik@hostserver.nu If not, that is totally OK as well. :-) //Henrik

JanEggers commented 6 years ago

the problem is that the new pipeline does not yet support encrypted communication. (there is no tls connection middleware yet and using sslstream negates all the performance benefits)

so you should remove

        o.ListenAnyIP(9010, l => l.UseMqtt()); // mqtt pipeline
        services.AddMqttConnectionHandler();

and instead add the old tcp server adapter with

  services.AddMqttTcpServerAdapter();
henkep commented 6 years ago

Thanks you soo much JanEggers!!

That solved the problem, it was driving me crazy. It works perfect now.

JanEggers commented 6 years ago

this is merged and aviable in 2.8.3.