From 04f2be01d910b2acf15e13c932aefb1a6fedec98 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 19 Dec 2020 13:33:46 +0100 Subject: [PATCH] dhcp4d: optionally publish DHCP leases to MQTT Enable using: mkdir -p /perm/dhcp4d echo 'tcp://10.0.0.54:1883' > /perm/dhcp4d/mqtt-broker.txt --- cmd/dhcp4d/dhcp4d.go | 26 ++++++++++++++++++++++ cmd/dhcp4d/mayqtt.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 1 + 4 files changed, 81 insertions(+) create mode 100644 cmd/dhcp4d/mayqtt.go diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index 8ed5f64..5c48e6f 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -234,6 +234,8 @@ type srv struct { } func newSrv(permDir string) (*srv, error) { + mayqtt := MQTT() + http.Handle("/metrics", promhttp.Handler()) if err := updateListeners(); err != nil { return nil, err @@ -399,6 +401,30 @@ func newSrv(permDir string) (*srv, error) { if err := notify.Process("/user/dnsd", syscall.SIGUSR1); err != nil { log.Printf("notifying dnsd: %v", err) } + + // Publish the DHCP lease as JSON to MQTT, if configured: + leaseVal := struct { + Addr string `json:"addr"` + HardwareAddr string `json:"hardware_addr"` + Expiration time.Time `json:"expiration"` + }{ + Addr: latest.Addr.String(), + HardwareAddr: latest.HardwareAddr, + Expiration: latest.Expiry.In(time.UTC), + } + leaseJSON, err := json.Marshal(leaseVal) + if err != nil { + log.Fatal(err) + } + identifier := latest.Hostname + if identifier == "" { + identifier = latest.HardwareAddr + } + mayqtt <- PublishRequest{ + Topic: "router7/dhcp4d/lease/" + identifier, + Retained: true, + Payload: leaseJSON, + } } conn, err := conn.NewUDP4BoundListener(*iface, ":67") if err != nil { diff --git a/cmd/dhcp4d/mayqtt.go b/cmd/dhcp4d/mayqtt.go new file mode 100644 index 0000000..ff5279d --- /dev/null +++ b/cmd/dhcp4d/mayqtt.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "io/ioutil" + "strings" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +type PublishRequest struct { + Topic string + Qos byte + Retained bool + Payload interface{} +} + +func publisherLoop(requests <-chan PublishRequest) error { + const configFn = "/perm/dhcp4d/mqtt-broker.txt" + b, err := ioutil.ReadFile(configFn) + if err != nil { + // discard requests: + for range requests { + } + return nil + } + // e.g. tcp://10.0.0.54:1883, which is a static DHCP lease for the dr.lan + // Raspberry Pi, which is running an MQTT broker in my network. + broker := strings.TrimSpace(string(b)) + log.Printf("Connecting to MQTT broker %q (configured in %s)", broker, configFn) + opts := mqtt.NewClientOptions().AddBroker(broker) + opts.SetClientID("dhcp4d") + mqttClient := mqtt.NewClient(opts) + if token := mqttClient.Connect(); token.Wait() && token.Error() != nil { + return fmt.Errorf("MQTT connection failed: %v", token.Error()) + } + + for r := range requests { + // discard Token, MQTT publishing is best-effort + _ = mqttClient.Publish(r.Topic, r.Qos, r.Retained, r.Payload) + } + return nil +} + +func MQTT() chan<- PublishRequest { + result := make(chan PublishRequest) + go func() { + if err := publisherLoop(result); err != nil { + log.Print(err) + } + }() + return result +} diff --git a/go.mod b/go.mod index f3de9d0..dbcb8f6 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/digineo/go-ping v1.0.0 github.com/gokrazy/breakglass v0.0.0-20200527163858-efff2172eebe // indirect github.com/gokrazy/gdns v0.0.0-20200218203540-6b3b6244ea39 // indirect + github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/gokrazy/gokrazy v0.0.0-20201006151115-caded4667633 github.com/gokrazy/internal v0.0.0-20200713084155-ab6fc6e02a03 // indirect github.com/gokrazy/timestamps v0.0.0-20200713073712-54fdc319126e // indirect diff --git a/go.sum b/go.sum index 776c349..715c89e 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,7 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=