From 3c451f06ca9692675a5e3cfdb8b78a616ffa6b69 Mon Sep 17 00:00:00 2001 From: lordwelch Date: Fri, 12 Jun 2020 17:53:13 -0700 Subject: [PATCH] Add the ability to run router7 on a normal Linux distribution --- Makefile | 9 ++++++ cmd/backupd/backupd.go | 10 +++++-- cmd/captured/captured.go | 5 ++++ cmd/dhcp4/dhcp4.go | 10 +++++-- cmd/dhcp4d/dhcp4d.go | 25 +++++++++------- cmd/dhcp6/dhcp6.go | 13 +++++---- cmd/diagd/diagd.go | 3 ++ cmd/dnsd/dnsd.go | 12 +++++--- cmd/dyndns/dyndns.go | 5 +++- cmd/netconfigd/netconfigd.go | 15 ++++++---- cmd/radvd/radvd.go | 9 ++++-- init/init.go | 51 +++++++++++++++++++++++++++++++++ internal/diag/dhcp.go | 7 +++-- internal/dns/dns.go | 1 + internal/netconfig/netconfig.go | 13 ++++++--- 15 files changed, 147 insertions(+), 41 deletions(-) create mode 100644 init/init.go diff --git a/Makefile b/Makefile index 332908c..efcdab1 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,15 @@ PKGS := github.com/rtr7/router7/cmd/... \ github.com/stapelberg/zkj-nas-tools/wolgw \ github.com/gokrazy/gdns +build: + mkdir -p result + GOOS=linux go build -o ./result github.com/rtr7/router7/cmd/... + GOOS=linux go build -o ./result/rtr7-init -ldflags "-X main.buildTimestamp=$(shell date '+%Y-%m-%dT%H:%M:%S%z') -X github.com/gokrazy/gokrazy.httpPassword=temp" init/init.go + +clean: + rm -rf result + go clean -cache + image: ifndef DIR @echo variable DIR unset diff --git a/cmd/backupd/backupd.go b/cmd/backupd/backupd.go index 016c682..a4ea893 100644 --- a/cmd/backupd/backupd.go +++ b/cmd/backupd/backupd.go @@ -16,6 +16,7 @@ package main import ( + "flag" "net" "net/http" "os" @@ -29,9 +30,12 @@ import ( "github.com/rtr7/router7/internal/teelogger" ) -var log = teelogger.NewConsole() +var ( + log = teelogger.NewConsole() + httpListeners = multilisten.NewPool() -var httpListeners = multilisten.NewPool() + perm = flag.String("perm", "/perm", "path to replace /perm") +) func updateListeners() error { hosts, err := gokrazy.PrivateInterfaceAddrs() @@ -47,7 +51,7 @@ func updateListeners() error { func logic() error { http.HandleFunc("/backup.tar.gz", func(w http.ResponseWriter, r *http.Request) { - if err := backup.Archive(w, "/perm"); err != nil { + if err := backup.Archive(w, *perm); err != nil { log.Printf("backup.tar.gz: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) } diff --git a/cmd/captured/captured.go b/cmd/captured/captured.go index ef9ab0d..223871f 100644 --- a/cmd/captured/captured.go +++ b/cmd/captured/captured.go @@ -24,6 +24,7 @@ import ( "log" "os" "os/signal" + "strings" "sync" "syscall" @@ -38,6 +39,7 @@ import ( ) var ( + perm = flag.String("perm", "/perm", "path to replace /perm") hostKeyPath = flag.String("host_key", "/perm/breakglass.host_key", "path to a PEM-encoded RSA, DSA or ECDSA private key (create using e.g. ssh-keygen -f /perm/breakglass.host_key -N '' -t rsa)") @@ -149,6 +151,9 @@ func logic() error { func main() { flag.Parse() + if *hostKeyPath == "/perm/breakglass.host_key" && *perm != "/perm" { + *hostKeyPath = strings.Replace(*hostKeyPath, "/perm", *perm, 1) + } if err := logic(); err != nil { log.Fatal(err) } diff --git a/cmd/dhcp4/dhcp4.go b/cmd/dhcp4/dhcp4.go index 38dc7d5..f09802b 100644 --- a/cmd/dhcp4/dhcp4.go +++ b/cmd/dhcp4/dhcp4.go @@ -24,7 +24,9 @@ import ( "net" "os" "os/signal" + "path" "path/filepath" + "strings" "syscall" "time" @@ -43,6 +45,7 @@ var log = teelogger.NewConsole() var ( netInterface = flag.String("interface", "uplink0", "network interface to operate on") stateDir = flag.String("state_dir", "/perm/dhcp4", "directory in which to store lease data (wire/lease.json) and last ACK (wire/ack)") + perm = flag.String("perm", "/perm", "path to replace /perm") ) func logic() error { @@ -59,7 +62,7 @@ func logic() error { // still use the old hardware address. We overwrite it with the address that // netconfigd is going to use to fix this issue without additional // synchronization. - details, err := netconfig.Interface("/perm", *netInterface) + details, err := netconfig.Interface(*perm, *netInterface) if err == nil { if spoof := details.SpoofHardwareAddr; spoof != "" { if addr, err := net.ParseMAC(spoof); err == nil { @@ -118,7 +121,7 @@ func logic() error { if err := renameio.WriteFile(ackFn, buf.Bytes(), 0644); err != nil { return fmt.Errorf("persisting DHCPACK to %s: %v", ackFn, err) } - if err := notify.Process("/user/netconfigd", syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "/netconfigd"), syscall.SIGUSR1); err != nil { log.Printf("notifying netconfig: %v", err) } select { @@ -138,6 +141,9 @@ func logic() error { func main() { // TODO: drop privileges, run as separate uid? flag.Parse() + if *stateDir == "/perm/dhcp4" && *perm != "/perm" { + *stateDir = strings.Replace(*stateDir, "/perm", *perm, 1) + } if err := logic(); err != nil { log.Fatal(err) } diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index 6babb70..c83200b 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -28,6 +28,7 @@ import ( "net/http" "os" "os/signal" + "path" "path/filepath" "sort" "strings" @@ -50,14 +51,16 @@ import ( "github.com/rtr7/router7/internal/teelogger" ) -var iface = flag.String("interface", "lan0", "ethernet interface to listen for DHCPv4 requests on") +var ( + log = teelogger.NewConsole() + nonExpiredLeases = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "non_expired_leases", + Help: "Number of non-expired DHCP leases", + }) -var log = teelogger.NewConsole() - -var nonExpiredLeases = promauto.NewGauge(prometheus.GaugeOpts{ - Name: "non_expired_leases", - Help: "Number of non-expired DHCP leases", -}) + iface = flag.String("interface", "lan0", "ethernet interface to listen for DHCPv4 requests on") + perm = flag.String("perm", "/perm", "path to replace /perm") +) func updateNonExpired(leases []*dhcp4d.Lease) { now := time.Now() @@ -71,7 +74,7 @@ func updateNonExpired(leases []*dhcp4d.Lease) { nonExpiredLeases.Set(float64(nonExpired)) } -var ouiDB = oui.NewDB("/perm/dhcp4d/oui") +var ouiDB = oui.NewDB(path.Join(*perm, "/dhcp4d/oui")) var ( leasesMu sync.Mutex @@ -218,7 +221,7 @@ func updateListeners() error { if err != nil { return err } - if net1, err := multilisten.IPv6Net1("/perm"); err == nil { + if net1, err := multilisten.IPv6Net1(*perm); err == nil { hosts = append(hosts, net1) } @@ -393,7 +396,7 @@ func newSrv(permDir string) (*srv, error) { errs <- err } updateNonExpired(leases) - if err := notify.Process("/user/dnsd", syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "/dnsd"), syscall.SIGUSR1); err != nil { log.Printf("notifying dnsd: %v", err) } } @@ -422,7 +425,7 @@ func (s *srv) run(ctx context.Context) error { func main() { // TODO: drop privileges, run as separate uid? flag.Parse() - srv, err := newSrv("/perm") + srv, err := newSrv(*perm) if err != nil { log.Fatal(err) } diff --git a/cmd/dhcp6/dhcp6.go b/cmd/dhcp6/dhcp6.go index b08729f..f51a09e 100644 --- a/cmd/dhcp6/dhcp6.go +++ b/cmd/dhcp6/dhcp6.go @@ -22,6 +22,7 @@ import ( "io/ioutil" "os" "os/signal" + "path" "path/filepath" "syscall" "time" @@ -35,15 +36,17 @@ import ( var log = teelogger.NewConsole() +var perm = flag.String("perm", "/perm", "path to replace /perm") + func logic() error { - const leasePath = "/perm/dhcp6/wire/lease.json" + leasePath := path.Join(*perm, "/dhcp6/wire/lease.json") if err := os.MkdirAll(filepath.Dir(leasePath), 0755); err != nil { return err } - duid, err := ioutil.ReadFile("/perm/dhcp6/duid") + duid, err := ioutil.ReadFile(path.Join(*perm, "/dhcp6/duid")) if err != nil { - log.Printf("could not read /perm/dhcp6/duid (%v), proceeding with DUID-LLT", err) + log.Printf("could not read %s (%v), proceeding with DUID-LLT", path.Join(*perm, "/dhcp6/duid"), err) } c, err := dhcp6.NewClient(dhcp6.ClientConfig{ @@ -78,10 +81,10 @@ func logic() error { if err := renameio.WriteFile(leasePath, b, 0644); err != nil { return err } - if err := notify.Process("/user/netconfigd", syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "/netconfigd"), syscall.SIGUSR1); err != nil { log.Printf("notifying netconfig: %v", err) } - if err := notify.Process("/user/radvd", syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "/radvd"), syscall.SIGUSR1); err != nil { log.Printf("notifying radvd: %v", err) } select { diff --git a/cmd/diagd/diagd.go b/cmd/diagd/diagd.go index 85fcda1..c3768e4 100644 --- a/cmd/diagd/diagd.go +++ b/cmd/diagd/diagd.go @@ -38,6 +38,8 @@ import ( var httpListeners = multilisten.NewPool() +var perm = flag.String("perm", "/perm", "path to replace /perm") + func updateListeners() error { hosts, err := gokrazy.PrivateInterfaceAddrs() if err != nil { @@ -134,6 +136,7 @@ func logic() error { func main() { flag.Parse() + diag.Perm = *perm if err := logic(); err != nil { log.Fatal(err) diff --git a/cmd/dnsd/dnsd.go b/cmd/dnsd/dnsd.go index 36f2011..36d2814 100644 --- a/cmd/dnsd/dnsd.go +++ b/cmd/dnsd/dnsd.go @@ -24,6 +24,7 @@ import ( "net/http" "os" "os/signal" + "path" "syscall" "github.com/gokrazy/gokrazy" @@ -40,6 +41,9 @@ import ( var ( httpListeners = multilisten.NewPool() dnsListeners = multilisten.NewPool() + + perm = flag.String("perm", "/perm", "path to replace /perm") + domain = flag.String("domain", "lan", "domain name for your network") ) func updateListeners(mux *miekgdns.ServeMux) error { @@ -56,7 +60,7 @@ func updateListeners(mux *miekgdns.ServeMux) error { }} }) - if net1, err := multilisten.IPv6Net1("/perm"); err == nil { + if net1, err := multilisten.IPv6Net1(*perm); err == nil { hosts = append(hosts, net1) } @@ -75,13 +79,13 @@ func (a *listenerAdapter) Close() error { return a.Shutdown() } func logic() error { // TODO: set correct upstream DNS resolver(s) - ip, err := netconfig.LinkAddress("/perm", "lan0") + ip, err := netconfig.LinkAddress(*perm, "lan0") if err != nil { return err } - srv := dns.NewServer(ip.String()+":53", "lan") + srv := dns.NewServer(ip.String()+":53", *domain) readLeases := func() error { - b, err := ioutil.ReadFile("/perm/dhcp4d/leases.json") + b, err := ioutil.ReadFile(path.Join(*perm, "/dhcp4d/leases.json")) if err != nil { return err } diff --git a/cmd/dyndns/dyndns.go b/cmd/dyndns/dyndns.go index 7e63dbc..cc61cbe 100644 --- a/cmd/dyndns/dyndns.go +++ b/cmd/dyndns/dyndns.go @@ -25,6 +25,7 @@ import ( "log" "net" "os" + "path" "time" "github.com/gokrazy/gokrazy" @@ -35,6 +36,8 @@ import ( var update = dyndns.Update +var perm = flag.String("perm", "/perm", "path to replace /perm") + type DynDNSRecord struct { // TODO: multiple providers support Cloudflare struct { @@ -105,7 +108,7 @@ func main() { var ( configFile = flag.String( "config_file", - "/perm/dyndns.json", + path.Join(*perm, "/dyndns.json"), "Path to the JSON configuration", ) diff --git a/cmd/netconfigd/netconfigd.go b/cmd/netconfigd/netconfigd.go index fbffd31..3d16b3c 100644 --- a/cmd/netconfigd/netconfigd.go +++ b/cmd/netconfigd/netconfigd.go @@ -21,6 +21,7 @@ import ( "net/http" "os" "os/signal" + "path" "sync" "syscall" @@ -39,7 +40,9 @@ import ( var log = teelogger.NewConsole() var ( - linger = flag.Bool("linger", true, "linger around after applying the configuration (until killed)") + linger = flag.Bool("linger", true, "linger around after applying the configuration (until killed)") + perm = flag.String("perm", "/perm", "path to replace /perm") + noFirewall = flag.Bool("nofirewall", false, "disable the rtr7 firewall") ) func init() { @@ -114,7 +117,7 @@ func updateListeners() error { if err != nil { return err } - if net1, err := multilisten.IPv6Net1("/perm"); err == nil { + if net1, err := multilisten.IPv6Net1(*perm); err == nil { hosts = append(hosts, net1) } @@ -134,19 +137,18 @@ func logic() error { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGUSR1) for { - err := netconfig.Apply("/perm/", "/") + err := netconfig.Apply(*perm, "/", !*noFirewall) // Notify dhcp4d so that it can update its listeners for prometheus // metrics on the external interface. - if err := notify.Process("/user/dhcp4d", syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "dhcp4d"), syscall.SIGUSR1); err != nil { log.Printf("notifying dhcp4d: %v", err) } // Notify gokrazy about new addresses (netconfig.Apply might have // modified state before returning an error) so that listeners can be // updated. - p, _ := os.FindProcess(1) - if err := p.Signal(syscall.SIGHUP); err != nil { + if err := notify.Process(path.Join(path.Dir(os.Args[0]), "rtr7-init"), syscall.SIGHUP); err != nil { log.Printf("kill -HUP 1: %v", err) } if err != nil { @@ -165,6 +167,7 @@ func logic() error { func main() { flag.Parse() + netconfig.CmdRoot = path.Dir(os.Args[0]) if err := logic(); err != nil { log.Fatal(err) } diff --git a/cmd/radvd/radvd.go b/cmd/radvd/radvd.go index 165640f..6caf9cd 100644 --- a/cmd/radvd/radvd.go +++ b/cmd/radvd/radvd.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Binary radvd sends IPv6 router advertisments. +// Binary radvd sends IPv6 router advertisements. package main import ( @@ -23,19 +23,22 @@ import ( "net" "os" "os/signal" + "path" "syscall" "github.com/rtr7/router7/internal/dhcp6" "github.com/rtr7/router7/internal/radvd" ) +var perm = flag.String("perm", "/perm", "path to replace /perm") + func logic() error { srv, err := radvd.NewServer() if err != nil { return err } readConfig := func() error { - b, err := ioutil.ReadFile("/perm/dhcp6/wire/lease.json") + b, err := ioutil.ReadFile(path.Join(*perm, "/dhcp6/wire/lease.json")) if err != nil { return err } @@ -45,7 +48,7 @@ func logic() error { } var additional []net.IPNet - if b, err := ioutil.ReadFile("/perm/radvd/prefixes.json"); err == nil { + if b, err := ioutil.ReadFile(path.Join(*perm, "/radvd/prefixes.json")); err == nil { if err := json.Unmarshal(b, &additional); err != nil { return err } diff --git a/init/init.go b/init/init.go new file mode 100644 index 0000000..e2dba0b --- /dev/null +++ b/init/init.go @@ -0,0 +1,51 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os/exec" + "path" + + "github.com/gokrazy/gokrazy" +) + +// buildTimestamp can be overridden by specifying e.g. +// -ldflags "-X main.buildTimestamp=foo" when building. +var ( + buildTimestamp = "2020-06-08T19:45:52-07:00" + + domain string + cmdRoot string + perm string + noFirewall bool +) + +func main() { + flag.StringVar(&cmdRoot, "cmdroot", "/usr/bin", "path to rtr7 binaries") + flag.StringVar(&domain, "domain", "lan", "domain name for your network") + flag.StringVar(&perm, "perm", "/var/lib/rtr7/", "path to replace /perm") + flag.BoolVar(&noFirewall, "nofirewall", false, "disable the rtr7 firewall") + flag.Parse() + log.SetFlags(log.LstdFlags | log.Lshortfile) + + fmt.Printf("gokrazy build timestamp %s\n", buildTimestamp) + + cmds := []*exec.Cmd{ + // exec.Command(path.Join(cmdRoot, "/ntp")), + exec.Command(path.Join(cmdRoot, "backupd"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "captured"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dhcp4"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dhcp4d"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dhcp6"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "diagd"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dnsd"), fmt.Sprintf("-domain=%s", domain), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dyndns"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "netconfigd"), fmt.Sprintf("-nofirewall=%t", noFirewall), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "radvd"), "-perm="+perm), + } + if err := gokrazy.Supervise(cmds); err != nil { + log.Fatal(err) + } + select {} +} diff --git a/internal/diag/dhcp.go b/internal/diag/dhcp.go index 2b7b2de..9e85151 100644 --- a/internal/diag/dhcp.go +++ b/internal/diag/dhcp.go @@ -18,9 +18,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "path" "time" ) +var Perm = "/perm" + func leaseValid(fn string) (status string, _ error) { var lease struct { ValidUntil time.Time `json:"valid_until"` @@ -56,7 +59,7 @@ func (d *dhcpv4) Children() []Node { } func (d *dhcpv4) Evaluate() (string, error) { - return leaseValid("/perm/dhcp4/wire/lease.json") + return leaseValid(path.Join(Perm, "/dhcp4/wire/lease.json")) } // DHCPv4 returns a Node which succeeds if /perm/dhcp4/wire/lease.json contains @@ -83,7 +86,7 @@ func (d *dhcpv6) Children() []Node { } func (d *dhcpv6) Evaluate() (string, error) { - return leaseValid("/perm/dhcp6/wire/lease.json") + return leaseValid(path.Join(Perm, "/dhcp6/wire/lease.json")) } // DHCPv6 returns a Node which succeeds if /perm/dhcp6/wire/lease.json contains diff --git a/internal/dns/dns.go b/internal/dns/dns.go index dc7d83d..fba1cd0 100644 --- a/internal/dns/dns.go +++ b/internal/dns/dns.go @@ -112,6 +112,7 @@ func NewServer(addr, domain string) *Server { server.initHostsLocked() server.Mux.HandleFunc(".", server.handleRequest) server.Mux.HandleFunc("lan.", server.handleInternal) + server.Mux.HandleFunc(domain+".", server.handleInternal) server.Mux.HandleFunc("localhost.", server.handleInternal) go func() { for range time.Tick(10 * time.Second) { diff --git a/internal/netconfig/netconfig.go b/internal/netconfig/netconfig.go index 53637d2..42fa663 100644 --- a/internal/netconfig/netconfig.go +++ b/internal/netconfig/netconfig.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "net" "os" + "path" "path/filepath" "regexp" "strconv" @@ -42,6 +43,8 @@ import ( var log = teelogger.NewConsole() +var CmdRoot = "/user" + func subnetMaskSize(mask string) (int, error) { parts := strings.Split(mask, ".") if got, want := len(parts), 4; got != want { @@ -729,7 +732,7 @@ func applySysctl(ifname string) error { return nil } -func Apply(dir, root string) error { +func Apply(dir, root string, firewall bool) error { // TODO: split into two parts: delay the up until later if err := applyInterfaces(dir, root); err != nil { @@ -757,7 +760,7 @@ func Apply(dir, root string) error { "backupd", // listens on private IPv4/IPv6 "captured", // listens on private IPv4/IPv6 } { - if err := notify.Process("/user/"+process, syscall.SIGUSR1); err != nil { + if err := notify.Process(path.Join(CmdRoot, process), syscall.SIGUSR1); err != nil { log.Printf("notifying %s: %v", process, err) } } @@ -771,8 +774,10 @@ func Apply(dir, root string) error { appendError(fmt.Errorf("sysctl: %v", err)) } - if err := applyFirewall(dir, ifname); err != nil { - appendError(fmt.Errorf("firewall: %v", err)) + if firewall { + if err := applyFirewall(dir, ifname); err != nil { + appendError(fmt.Errorf("firewall: %v", err)) + } } if err := applyWireGuard(dir); err != nil {