Skip to content

Commit

Permalink
feat: reading UDP and parse DNS
Browse files Browse the repository at this point in the history
  • Loading branch information
grishy committed Aug 7, 2023
1 parent dfba1b2 commit 5ff3e5c
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 46 deletions.
61 changes: 38 additions & 23 deletions cmd/cname.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"log"
"os"
"time"

"github.com/grishy/go-avahi-cname/publisher"
Expand All @@ -19,7 +18,7 @@ func formatCname(hostnameFqdn string, cnames []string) []string {
if !dns.IsFqdn(cname) {
cnames[i] = dns.Fqdn(cname + "." + hostnameFqdn)

log.Printf(" > '%s' (added current FQDN)", cnames[i])
log.Printf(" > '%s' (added FQDN)", cnames[i])
continue
}

Expand All @@ -29,41 +28,58 @@ func formatCname(hostnameFqdn string, cnames []string) []string {
return cnames
}

func publishing(ctx context.Context, publisher *publisher.Publisher, ttl, interval uint32, cnames []string) {
func publishing(ctx context.Context, publisher *publisher.Publisher, cnames []string, ttl, interval uint32) error {
log.Printf("Publishing every %ds and CNAME TTL %ds", interval, ttl)

resendDuration := time.Duration(interval) * time.Second
log.Printf("Publishing every %v and CNAME TTL=%ds.", resendDuration, ttl)
ticker := time.NewTicker(resendDuration)
defer ticker.Stop()

// To start publishing immediately
// https://github.com/golang/go/issues/17601
if err := publisher.PublishCNAMES(cnames, ttl); err != nil {
log.Fatalf("can't publish CNAMEs: %v", err)
return fmt.Errorf("can't publish CNAMEs: %w", err)
}

for {
select {
case <-time.Tick(resendDuration):
case <-ticker.C:
if err := publisher.PublishCNAMES(cnames, ttl); err != nil {
log.Fatalf("can't publish CNAMEs: %v", err)
return fmt.Errorf("can't publish CNAMEs: %w", err)
}
case <-ctx.Done():
fmt.Println()
fmt.Println() // Add new line after ^C
log.Println("Closing publisher...")
if err := publisher.Close(); err != nil {
log.Fatalf("Can't close publisher: %v", err)
return fmt.Errorf("can't close publisher: %w", err)
}
os.Exit(0)
return nil
}
}
}

func cnameCmd(ctx context.Context, ttl, interval uint32, cnames []string) {
func runCname(ctx context.Context, cnames []string, fqdn string, ttl, interval uint32) error {
log.Println("Creating publisher")
publisher, err := publisher.NewPublisher()
if err != nil {
return fmt.Errorf("can't create publisher: %w", err)
}

if fqdn == "" {
log.Println("Getting FQDN from Avahi")
fqdn = publisher.Fqdn()
}

log.Printf("FQDN: %s", fqdn)

formattedCname := formatCname(fqdn, cnames)
return publishing(ctx, publisher, formattedCname, ttl, interval)
}

func CmdCname(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "cname",
Usage: "anonse CNAME via Avahi",
Usage: "Anounce CNAME records for current host via Avahi. Require DBus connection to Avahi daemon.",
Flags: []cli.Flag{
&cli.UintFlag{
Name: "ttl",
Expand All @@ -75,27 +91,26 @@ func CmdCname(ctx context.Context) *cli.Command {
Name: "interval",
Value: 300,
EnvVars: []string{"CNAME_INTERVAL"},
Usage: "Interval of sending CNAME record in seconds",
Usage: "Interval of publishing CNAME records in seconds",
},
&cli.StringFlag{
Name: "fqdn",
EnvVars: []string{"SUBDOMAIN_FQDN"},
Usage: "FQDN which will be used for CNAME. If empty, will be used current FQDN",
DefaultText: "hostname.local.",
},
},
Action: func(cCtx *cli.Context) error {
ttl := uint32(cCtx.Uint("ttl"))
interval := uint32(cCtx.Uint("interval"))
fqdn := cCtx.String("fqdn")
cnames := cCtx.Args().Slice()

if len(cnames) == 0 {
log.Fatal("CNAMEs are not specified")
return fmt.Errorf("at least one CNAME should be provided")
}

log.Println("Creating publisher")
publisher, err := publisher.NewPublisher()
if err != nil {
log.Fatalf("Can't create publisher: %v", err)
}

formattedCname := formatCname(publisher.Fqdn(), cnames)
publishing(ctx, publisher, ttl, interval, formattedCname)
return nil
return runCname(ctx, cnames, fqdn, ttl, interval)
},
}
}
133 changes: 115 additions & 18 deletions cmd/subdomain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,134 @@ package cmd

import (
"context"
"fmt"
"log"
"net"

"github.com/godbus/dbus/v5"
"github.com/holoplot/go-avahi"
"github.com/miekg/dns"
"github.com/urfave/cli/v2"
)

func getFqdn() (string, error) {
conn, err := dbus.SystemBus()
if err != nil {
return "nil", fmt.Errorf("can't connect to system bus: %v", err)
}

server, err := avahi.ServerNew(conn)
if err != nil {
return "nil", fmt.Errorf("can't create Avahi server: %v", err)
}

avahiFqdn, err := server.GetHostNameFqdn()
if err != nil {
return "nil", fmt.Errorf("can't get FQDN from Avahi: %v", err)
}

return avahiFqdn, nil
}

func listen() (*net.UDPConn, error) {
addr := &net.UDPAddr{
IP: net.ParseIP("224.0.0.251"),
Port: 5353,
}

conn, err := net.ListenMulticastUDP("udp4", nil, addr)
if err != nil {
return nil, err
}

return conn, nil
}

func readMessage(ctx context.Context, conn *net.UDPConn) (chan *dns.Msg, chan error) {
buf := make([]byte, 1500)

msgCh := make(chan *dns.Msg)
errCh := make(chan error)

go func() {
<-ctx.Done()
fmt.Println() // Add new line after ^C
log.Println("Closing reader")

conn.Close()
close(msgCh)
close(errCh)
}()

go func() {
for {
read, addr, err := conn.ReadFromUDP(buf)
if err != nil {
errCh <- fmt.Errorf("can't read from UDP from %s: %w", addr, err)
continue
}

msg := new(dns.Msg)
if err := msg.Unpack(buf[:read]); err != nil {
errCh <- fmt.Errorf("can't unpack message from %s: %w", addr, err)
continue
}

msgCh <- msg
}
}()

return msgCh, errCh
}

func runSubdomain(ctx context.Context, fqdn string) error {
log.Println("Starting subdomain...")

l, err := listen()
if err != nil {
return fmt.Errorf("can't listen: %w", err)
}
defer l.Close()

msgCh, errCh := readMessage(ctx, l)
// TODO: Pack to one struct, msg and error
_ = errCh

for msg := range msgCh {
if len(msg.Question) > 0 {
log.Printf("Received question: %s", msg.Question[0].Name)
}
}

return nil
}

func CmdSubdomain(ctx context.Context) *cli.Command {
return &cli.Command{
Name: "subdomain",
Usage: "reply on all subdomains queries",
Usage: "Listen for all queries and publish CNAMEs for subdomains",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "domain",
Value: "",
EnvVars: []string{"SUBDOMAIN_DOMAIN"},
Usage: "Domain name to publish",
},
&cli.StringSliceFlag{
Name: "ifaces",
Value: nil,
EnvVars: []string{"SUBDOMAIN_IFACES"},
Usage: "Interface for listening and publishing",
},
&cli.BoolFlag{
Name: "use-avahi",
Value: true,
EnvVars: []string{"SUBDOMAIN_USE_AVAHI"},
Usage: "Use avahi for sending CNAMEs or plain DNS",
Name: "fqdn",
EnvVars: []string{"SUBDOMAIN_FQDN"},
Usage: "FQDN which will be used for CNAME. If empty, will be used current FQDN",
DefaultText: "hostname.local.",
},
},
Action: func(cCtx *cli.Context) error {
fqdn := cCtx.String("fqdn")

if fqdn == "" {
var err error
fqdn, err = getFqdn()
if err != nil {
return fmt.Errorf("can't get FQDN: %w", err)
}
}

log.Printf("FQDN: %s", fqdn)

return nil
return runSubdomain(ctx, fqdn)
},
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/grishy/go-avahi-cname
go 1.20

require (
github.com/carlmjohnson/versioninfo v0.22.5
github.com/godbus/dbus/v5 v5.1.0
github.com/holoplot/go-avahi v1.0.1
github.com/miekg/dns v1.1.55
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc=
github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
Expand Down
13 changes: 8 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,31 @@ package main

import (
"context"
"log"
"fmt"
"os"
"os/signal"

"github.com/carlmjohnson/versioninfo"
"github.com/grishy/go-avahi-cname/cmd"
"github.com/urfave/cli/v2"
)

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()

app := &cli.App{
Name: "go-avahi-cname",
Usage: "make an explosive entrance",
Name: "go-avahi-cname",
Usage: "Additional functionality for Avahi's mDNS responder",
Version: versioninfo.Short(),
Commands: []*cli.Command{
cmd.CmdCname(ctx),
cmd.CmdSubdomain(ctx),
},
}

if err := app.Run(os.Args); err != nil {
log.Fatal(err)
fmt.Println("Error:")
fmt.Printf(" > %+v\n", err)
}
}

0 comments on commit 5ff3e5c

Please sign in to comment.