Hello world

This commit is contained in:
Ben Meadors
2025-04-16 09:04:28 -05:00
parent 06e9955a16
commit d9632d3501
10 changed files with 424 additions and 3 deletions

11
.dockerignore Normal file
View File

@@ -0,0 +1,11 @@
bin/
obj/
*.user
*.suo
*.log
.vs/
.vscode/
.git/
.gitignore
Dockerfile
.dockerignore

3
.gitignore vendored
View File

@@ -51,4 +51,5 @@ CodeCoverage/
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
nunit-*.xml
.DS_Store

23
Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy csproj and restore dependencies
COPY Meshtastic.Mqtt.csproj ./
RUN dotnet restore
# Copy the rest of the code
COPY . ./
RUN dotnet publish -c Release -o /app
# Build runtime image
FROM mcr.microsoft.com/dotnet/runtime:9.0
WORKDIR /app
COPY --from=build /app ./
# Expose ports
EXPOSE 1883 8883
# Set environment variable to control SSL mode
# ENV SSL=true # Uncomment to enable SSL by default
ENTRYPOINT ["dotnet", "Meshtastic.Mqtt.dll"]

30
Meshtastic.Mqtt.csproj Normal file
View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meshtastic" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.2" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.2" />
<PackageReference Include="MQTTnet" Version="5.0.1.1416" />
<PackageReference Include="MQTTnet.Server" Version="5.0.1.1416" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="certificate.pfx;cert.pem">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

24
Meshtastic.Mqtt.sln Normal file
View File

@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Meshtastic.Mqtt", "Meshtastic.Mqtt.csproj", "{7467C293-5E7D-1ADF-451D-7E36BDAC9410}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7467C293-5E7D-1ADF-451D-7E36BDAC9410}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7467C293-5E7D-1ADF-451D-7E36BDAC9410}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7467C293-5E7D-1ADF-451D-7E36BDAC9410}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7467C293-5E7D-1ADF-451D-7E36BDAC9410}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {62FE50C6-004E-4C4D-BE74-89E28A6A1064}
EndGlobalSection
EndGlobal

178
Program.cs Normal file
View File

@@ -0,0 +1,178 @@
using MQTTnet.Server;
using Meshtastic.Protobufs;
using Google.Protobuf;
using Serilog;
using MQTTnet.Protocol;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog.Formatting.Compact;
using Meshtastic.Crypto;
using Meshtastic;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
var mqttFactory = new MqttServerFactory();
// #if SSL
var currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
#pragma warning disable SYSLIB0057 // Type or member is obsolete
var certificate = new X509Certificate2(Path.Combine(currentPath, "certificate.pfx"), "large4cats", X509KeyStorageFlags.Exportable);
#pragma warning restore SYSLIB0057 // Type or member is obsolete
var mqttServerOptions = new MqttServerOptionsBuilder()
.WithoutDefaultEndpoint() // This call disables the default unencrypted endpoint on port 1883
.WithEncryptedEndpoint()
.WithEncryptedEndpointPort(8883)
.WithEncryptionCertificate(certificate.Export(X509ContentType.Pfx))
.WithEncryptionSslProtocol(SslProtocols.Tls12)
.Build();
Log.Logger.Information("Using SSL certificate for MQTT server");
// If you want to use a non-encrypted MQTT server, you can uncomment the following lines instead of the above
// var mqttServerOptions = new MqttServerOptionsBuilder()
// .WithDefaultEndpoint()
// .WithDefaultEndpointPort(1883)
// .Build();
// Log.Logger.Information("Using unencrypted MQTT server");
// #endif
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(new RenderedCompactJsonFormatter())
// .WriteTo.File(new RenderedCompactJsonFormatter(), "log.json", rollingInterval: RollingInterval.Hour) // File logging can be enabled if needed
.CreateLogger();
using var mqttServer = mqttFactory.CreateMqttServer(mqttServerOptions);
static Data? DecryptMeshPacket(ServiceEnvelope serviceEnvelope)
{
var nonce = new NonceGenerator(serviceEnvelope.Packet.From, serviceEnvelope.Packet.Id).Create();
var decrypted = PacketEncryption.TransformPacket(serviceEnvelope.Packet.Encrypted.ToByteArray(), nonce, Resources.DEFAULT_PSK);
var payload = Data.Parser.ParseFrom(decrypted);
if (payload.Portnum > PortNum.UnknownApp && payload.Payload.Length > 0)
return payload;
// Was not able to decrypt the payload
return null;
}
mqttServer.InterceptingPublishAsync += async (args) =>
{
try
{
if (args.ApplicationMessage.Payload.Length == 0)
{
Log.Logger.Warning("Received empty payload on topic {@Topic} from {@ClientId}", args.ApplicationMessage.Topic, args.ClientId);
args.ProcessPublish = false; // This will block empty packets
return;
}
var serviceEnvelope = ServiceEnvelope.Parser.ParseFrom(args.ApplicationMessage.Payload);
// Block malformed service envelopes / packets
if (
String.IsNullOrWhiteSpace(serviceEnvelope.ChannelId) ||
String.IsNullOrWhiteSpace(serviceEnvelope.GatewayId) ||
serviceEnvelope.Packet == null ||
serviceEnvelope.Packet.Id < 1 ||
serviceEnvelope.Packet.From < 1 ||
serviceEnvelope.Packet.Encrypted == null ||
serviceEnvelope.Packet.Encrypted.Length < 1 ||
serviceEnvelope.Packet.Decoded != null)
{
Log.Logger.Warning("Service envelope or packet is malformed. Blocking packet on topic {@Topic} from {@ClientId}", args.ApplicationMessage.Topic, args.ClientId);
args.ProcessPublish = false;
return;
}
var data = DecryptMeshPacket(serviceEnvelope);
// If we were not able to decrypt the packet, it is likely encrypted with an unknown PSK
// Uncomment the following lines if you want to block these packets
// if (data == null)
// {
// Log.Logger.Warning("Service envelope does not contain a valid packet. Blocking packet");
// args.ProcessPublish = false; // This will block packets that are not valid protobuf packets
// return;
// }
if (data?.Portnum == PortNum.TextMessageApp)
{
Log.Logger.Information("Received text message on topic {@Topic} from {@ClientId}: {@Message}",
args.ApplicationMessage.Topic, args.ClientId, data.Payload.ToStringUtf8());
}
else
{
Log.Logger.Information("Received packet on topic {@Topic} from {@ClientId} with port number: {@Portnum}",
args.ApplicationMessage.Topic, args.ClientId, data?.Portnum);
}
// Any further validation logic to block a packet can be added here
args.ProcessPublish = true;
}
catch (InvalidProtocolBufferException)
{
Log.Logger.Warning("Failed to decode presumed protobuf packet. Blocking");
args.ProcessPublish = false; // This will block packets encrypted on unknown PSKs
}
catch (Exception ex)
{
Log.Logger.Error("Exception occured while attempting to decode packet on {@Topic} from {@ClientId}: {@Exception}", args.ApplicationMessage.Topic, args.ClientId, ex.Message);
args.ProcessPublish = false; // This will block packets that caused us to encounter an exception
}
};
mqttServer.InterceptingSubscriptionAsync += (args) =>
{
args.ProcessSubscription = true; // Subscription filtering logic can be added here to only allow certain topics
return Task.CompletedTask;
};
mqttServer.ValidatingConnectionAsync += (args) =>
{
args.ReasonCode = true ? // Authentication logic can be added here
MqttConnectReasonCode.Success : MqttConnectReasonCode.BadUserNameOrPassword;
// You can block connections based on client ID, username, ip, etc.
return Task.CompletedTask;
};
static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.UseConsoleLifetime()
.ConfigureServices((hostContext, services) =>
{
services
.AddSingleton(Console.Out);
});
}
using var host = CreateHostBuilder(args).Build();
await host.StartAsync();
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
await mqttServer.StartAsync();
var ended = new ManualResetEventSlim();
var starting = new ManualResetEventSlim();
AssemblyLoadContext.Default.Unloading += ctx =>
{
starting.Set();
Log.Logger.Debug("Waiting for completion");
ended.Wait();
};
starting.Wait();
Log.Logger.Debug("Received signal gracefully shutting down");
await mqttServer.StopAsync();
Thread.Sleep(1000);
ended.Set();
lifetime.StopApplication();
await host.WaitForShutdownAsync();

View File

@@ -1,2 +1,71 @@
# mqtt
A meshtastic native packet aware MQTT broker
# Meshtastic MQTT Broker Boilerplate
This project provides an MQTT broker boilerplate specifically designed for Meshtastic device networks. It handles encrypted mesh packets, validates messages, and can be configured to run with or without SSL.
## Features
- MQTT server implementation for Meshtastic devices
- Support for encrypted mesh packet handling and validation
- SSL support for secure MQTT connections
- Configurable logging with Serilog
- Packet filtering and validation logic
## Docker Setup
### Prerequisites
- Docker installed on your system
- Certificate file (if using SSL mode)
### Docker Installation
1. Clone the repository:
```bash
git clone https://github.com/meshtastic/mqtt
cd mqtt
```
2. Build the Docker image:
```bash
docker build -t meshtastic-mqtt-broker .
```
#### SSL Mode (Port 8883)
To run with SSL enabled:
1. Place your certificate file (`certificate.pfx`) in the project directory. (see [MQTTnet Server Wiki](https://github.com/dotnet/MQTTnet/wiki/Server))
2. Run the container with the SSL environment variable:
```bash
docker run -p 8883:8883 -v $(pwd)/certificate.pfx:/app/certificate.pfx meshtastic-mqtt-broker
```
### Docker Compose Example
```yaml
version: '3'
services:
mqtt-broker:
build: .
ports:
- "1883:1883" # Standard MQTT port
# - "8883:8883" # SSL port (uncomment if using SSL)
# environment:
# - SSL=true # Uncomment to enable SSL
# volumes:
# - ./certificate.pfx:/app/certificate.pfx # Mount certificate if using SSL
restart: unless-stopped
```
## Configuration Options
- **SSL**: Set environment variable `SSL=true` to enable SSL mode
- **Certificate**: Mount your PFX certificate file to `/app/certificate.pfx` in the container
- **Ports**: The application uses port 1883 for standard MQTT and 8883 for SSL MQTT
## Troubleshooting
- Ensure proper network access to the Docker container
- Check that certificates are correctly formatted (for SSL mode)
- Review logs using `docker logs [container-id]`

31
cert.pem Normal file
View File

@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUAiOTlcvZA8d3i1YXISMcn+B4+PowDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTA0MTYwMTQwNThaFw0yNjA0
MTYwMTQwNThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC5qV6+hqXx9g3dEAWZ9ZfBL8K5s2M1JvPakRu4MW9l
My1Ugf6mAB/7FkY168STGU3/8r0a+E743eTaPGWrlhBY1tEfCYwXLi2+xiL3yOB1
+6uzla7+THOM3G3CN/Hoq0zw6IB2PJQF2ZT8rDLbmYdvv8i4hWSHjxNU/cEK2/aP
rMhKv8uWieEmstDCoF8AKxpFvh9JYhyeDTF5w/2cPEP+LcR36F8rVr9EbiieJ27U
GDvfgDe+Vdjm4YDVPVswf8/goN+TehjAOMQx1BHQLEFEij9KBmYdl6+mQ2mMnlwK
YjZJ36133QolH00x/yYGEak/DS21gECMBbTUw3Y6hs6wdWmV/cHRV7tIrBKGkMxG
0RSdqgukDfXRzi7kPO7TuMu5ju2ifS7JKhhNrh0IbuHFKSNjFYW8tTaXNHBuumKo
GUH51XjlONN43kTDzKMHkPznDImrKKL3PhRqFXUdgiwSoRNHY6/XHPAW6ecJ7Jla
hctnLQFjf4zsRkgTQjlj+fz9JPQTljhFQUkQyotnnp2eC23ceX/dnKG4hz350GkP
zL31vQ62f2BPOHacaLVCJrLBffOJjyAqfvR2J0oGPUo+7iuvjurC2pq9wgKaLLbQ
Y3ncHaBD3bLZk/KzrirX8Rn8cPbCs1Q1Ehe+F2mbarRjSsGuOlEqatns3gYeIZSd
SwIDAQABo1MwUTAdBgNVHQ4EFgQUczeKEnJEd8rhlPzTSDhNUircBvUwHwYDVR0j
BBgwFoAUczeKEnJEd8rhlPzTSDhNUircBvUwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAWTroM00DTyqsxj/SSAM9jx1XZXfEi5ebqCDawROmTdhF
ZnmoQ2wqtRU/B6en2GTah/RKOcAyby/Pgtd3X3/x+J1W1D2uH5wNlhCxlH8X+GbW
2Br4nRKlMxcg1TbHVowPybV5loAcGjiCuIsmk0Iq4M1WUn/Ex6SZENT6MxT3yYVS
neQ5NZhk6luUCpjxgY3XT2tclagM2i5YVVWT8kYQakmtRteBZmK4ZrFfc486/rNj
4hLX8kwhRriz6U2mCtNCLB1kMF5imDHnIBH2winOEmqnN2pEzC15PDFOvRMoJzIl
lXk3Odnv3BXIHosLJQheTsAMFrTDf82itVwoYFMx8QL/0jvYrVkdhzbZMJEcN6Bi
PeOcji3mqcOm1JKsYNPz9V/U0faoixOekG7y7PnW8cEsag9lrGPxr0rGJ9Y431O2
GIV9D79WY25WWn23+R3VGZWrnOUY33UHl3ZngXnBSvyLMHP6ZSb/SuTM6hMPbqaB
uauhTqdXUc1P90hTToBz1wtbKwoYBYIM7sGZP8IFiFdCSaxX1Kckks6YELFxZWFN
1AWOG5GFT4xOGDgcBnbHcldejKEO6fRa+D0SSucyJ84EK1tvhKAMxPXnp73t4eE/
gwnGZ/9oLm5RRjAFZeCFCjNgbA2bRTML8MqGYuaBSKMQX4cG2dSeJDH29vvj5tg=
-----END CERTIFICATE-----

BIN
certificate.pfx Normal file

Binary file not shown.

54
key.pem Normal file
View File

@@ -0,0 +1,54 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
rH1xuKTR/w3KS06Rjacob21XXWL8FRkS
-----END ENCRYPTED PRIVATE KEY-----