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
This commit is contained in:
Michael Stapelberg 2020-12-19 13:33:46 +01:00
parent e5ea79aef8
commit 04f2be01d9
4 changed files with 81 additions and 0 deletions

View File

@ -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 {

53
cmd/dhcp4d/mayqtt.go Normal file
View File

@ -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
}

1
go.mod
View File

@ -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

1
go.sum
View File

@ -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=