From 69200acf90e6ed48072354141e2a11fdbce35bb4 Mon Sep 17 00:00:00 2001 From: lordwelch Date: Sat, 22 Aug 2020 10:36:12 -0700 Subject: [PATCH] Implement NTP server from DHCP --- cmd/dhcp/dhcp.go | 44 ++++++++++++++++++++++++++++-- cmd/ntp/ntp.go | 43 ++++++++++++++++++++++++++++-- cmd/ntp/privdrop.go | 10 +++++++ go.mod | 1 + go.sum | 2 ++ internal/notify/notify.go | 56 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 internal/notify/notify.go diff --git a/cmd/dhcp/dhcp.go b/cmd/dhcp/dhcp.go index d855f0f..50faa9e 100644 --- a/cmd/dhcp/dhcp.go +++ b/cmd/dhcp/dhcp.go @@ -9,6 +9,7 @@ package main import ( "bytes" + "encoding/json" "flag" "fmt" "io/ioutil" @@ -20,8 +21,11 @@ import ( "syscall" "time" + "internal/notify" + "github.com/gokrazy/gokrazy/internal/iface" "github.com/google/gopacket/layers" + "github.com/google/renameio" "github.com/mdlayher/raw" "github.com/rtr7/dhcp4" "golang.org/x/sys/unix" @@ -64,7 +68,9 @@ func (c *client) discover() (*layers.DHCPv4, error) { layers.DHCPOptDNS, layers.DHCPOptRouter, layers.DHCPOptSubnetMask, - layers.DHCPOptDomainName), + layers.DHCPOptDomainName, + layers.DHCPOptNTPServers, + ), }) if err := dhcp4.Write(c.conn, discover); err != nil { return nil, err @@ -101,7 +107,9 @@ func (c *client) request(last *layers.DHCPv4) (*layers.DHCPv4, error) { layers.DHCPOptDNS, layers.DHCPOptRouter, layers.DHCPOptSubnetMask, - layers.DHCPOptDomainName), + layers.DHCPOptDomainName, + layers.DHCPOptNTPServers, + ), }, dhcp4.ServerID(last.Options)...)) if err := dhcp4.Write(c.conn, request); err != nil { return nil, err @@ -127,6 +135,11 @@ func (c *client) request(last *layers.DHCPv4) (*layers.DHCPv4, error) { } } +var permDir = flag.String( + "perm", + "/perm", + "path to replace /perm") + func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) var ( @@ -137,6 +150,10 @@ func main() { ) flag.Parse() + if err := os.MkdirAll(filepath.Join(*permDir, "dhcp4"), 0755); err != nil { + log.Println(err) + } + // NOTE: cannot gokrazy.WaitForClock() here, since the clock can only be // initialized once the network is up. @@ -197,6 +214,7 @@ func main() { } lease := dhcp4.LeaseFromACK(last) + saveLease(lease) // Log the received DHCPACK packet: details := []string{ @@ -218,6 +236,9 @@ func main() { if len(lease.Broadcast) > 0 { details = append(details, fmt.Sprintf("broadcast %v", lease.Broadcast)) } + if len(lease.NTP) > 0 { + details = append(details, fmt.Sprintf("NTP %v", lease.NTP)) + } log.Printf("DHCPACK: %v", strings.Join(details, ", ")) @@ -286,3 +307,22 @@ func main() { time.Sleep(lease.RenewalTime) } } + +func saveLease(lease dhcp4.Lease) { + b, err := json.Marshal(lease) + if err != nil { + log.Println(err) + return + } + var out bytes.Buffer + if err := json.Indent(&out, b, "", "\t"); err == nil { + b = out.Bytes() + } + if err := renameio.WriteFile(filepath.Join(*permDir, "dhcp4/lease.json"), b, 0644); err != nil { + log.Println(err) + return + } + if err := notify.Process(filepath.Join(filepath.Dir(os.Args[0]), "/ntp"), syscall.SIGUSR1); err != nil { + log.Printf("notifying ntp: %v", err) + } +} diff --git a/cmd/ntp/ntp.go b/cmd/ntp/ntp.go index e48dd98..a81d13b 100644 --- a/cmd/ntp/ntp.go +++ b/cmd/ntp/ntp.go @@ -2,17 +2,28 @@ package main import ( + "encoding/json" + "flag" + "io/ioutil" "log" "math/rand" + "net" "os" + "os/signal" + "path/filepath" "syscall" "time" "github.com/beevik/ntp" + "github.com/rtr7/dhcp4" ) +var server = "0.gokrazy.pool.ntp.org" + +var permDir = flag.String("perm", "/perm", "path to replace /perm") + func set(rtc *os.File) error { - r, err := ntp.Query("0.gokrazy.pool.ntp.org") + r, err := ntp.Query(server) if err != nil { return err } @@ -21,7 +32,7 @@ func set(rtc *os.File) error { if err := syscall.Settimeofday(&tv); err != nil { return err } - log.Printf("clock set to %v", r.Time) + log.Printf("clock set to %v using %v", r.Time, server) if rtc == nil { return nil @@ -30,6 +41,7 @@ func set(rtc *os.File) error { } func main() { + flag.Parse() log.SetFlags(log.LstdFlags | log.Lshortfile) var rtc *os.File @@ -47,6 +59,15 @@ func main() { mustDropPrivileges(rtc) + go func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + for range ch { + loadNTPServer() + } + }() + loadNTPServer() + for { if err := set(rtc); err != nil { log.Fatalf("setting time failed: %v", err) @@ -54,3 +75,21 @@ func main() { time.Sleep(1*time.Hour + time.Duration(rand.Int63n(250))*time.Millisecond) } } + +func loadNTPServer() { + var lease dhcp4.Lease + file, err := ioutil.ReadFile(filepath.Join(*permDir, "dhcp4/lease.json")) + if err != nil { + log.Println(err) + return + } + err = json.Unmarshal(file, &lease) + if err != nil { + log.Println(err) + return + } + if len(lease.DNS) > 0 && !lease.DNS[0].To4().Equal(net.IPv4zero) { + server = lease.DNS[0].String() + log.Printf("Setting ntp server to: %s", server) + } +} diff --git a/cmd/ntp/privdrop.go b/cmd/ntp/privdrop.go index 9b35c12..0576cdd 100644 --- a/cmd/ntp/privdrop.go +++ b/cmd/ntp/privdrop.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/exec" + "os/signal" "syscall" "unsafe" ) @@ -84,5 +85,14 @@ func mustDropPrivileges(rtc *os.File) { }, AmbientCaps: []uintptr{CAP_SYS_TIME}, } + + go func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch) + for sig := range ch { + cmd.Process.Signal(sig) + } + }() + log.Fatal(cmd.Run()) } diff --git a/go.mod b/go.mod index 827d4bf..1495516 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/gokrazy/internal v0.0.0-20200531194636-d96421c60091 github.com/google/go-cmp v0.4.0 // indirect github.com/google/gopacket v1.1.16 + github.com/google/renameio v0.1.0 github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b github.com/rtr7/dhcp4 v0.0.0-20181120124042-778e8c2e24a5 diff --git a/go.sum b/go.sum index 7ea0c2f..335a3a3 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gopacket v1.1.16 h1:u6Afvia5C5srlLcbTwpHaFW918asLYPxieziOaWwz8M= github.com/google/gopacket v1.1.16/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af h1:20h/EjkLGn9mV5nX9MFnGhbbeEhIGnOKPShJfBtVkVQ= github.com/mdlayher/raw v0.0.0-20190303161257-764d452d77af/go.mod h1:rC/yE65s/DoHB6BzVOUBNYBGTg772JVytyAytffIZkY= github.com/mdlayher/watchdog v0.0.0-20201005150459-8bdc4f41966b h1:7tUBfsEEBWfFeHOB7CUfoOamak+Gx/BlirfXyPk1WjI= diff --git a/internal/notify/notify.go b/internal/notify/notify.go new file mode 100644 index 0000000..08cd2d4 --- /dev/null +++ b/internal/notify/notify.go @@ -0,0 +1,56 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package notify implements sending signals (such as SIGUSR1) to processes. +package notify + +import ( + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +var numericRe = regexp.MustCompile(`^[0-9]+$`) + +func Process(name string, sig os.Signal) error { + fis, err := ioutil.ReadDir("/proc") + if err != nil { + return err + } + for _, fi := range fis { + if !fi.IsDir() { + continue + } + if !numericRe.MatchString(fi.Name()) { + continue + } + b, err := ioutil.ReadFile(filepath.Join("/proc", fi.Name(), "cmdline")) + if err != nil { + if os.IsNotExist(err) { + continue // process vanished + } + return err + } + if !strings.Contains(string(b), name) { + continue + } + pid, _ := strconv.Atoi(fi.Name()) // already verified to be numeric + p, _ := os.FindProcess(pid) + return p.Signal(sig) + } + return nil +}