diff --git a/.gitignore b/.gitignore index ff7864d..4e50810 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ bazel-bin bazel-kdhcp bazel-out bazel-testlogs + +kdhcpd +cmd/kdhcpd/kdhcpd diff --git a/cmd/kdhcpd/main.go b/cmd/kdhcpd/main.go index 84c32cc..4db973f 100644 --- a/cmd/kdhcpd/main.go +++ b/cmd/kdhcpd/main.go @@ -1,36 +1,34 @@ package main import ( + "context" "flag" + "os" + "git.wntrmute.dev/kyle/kdhcp/bazel-kdhcp/external/com_github_davecgh_go_spew/spew" + "git.wntrmute.dev/kyle/kdhcp/config" "git.wntrmute.dev/kyle/kdhcp/log" - "git.wntrmute.dev/kyle/kdhcp/server" - "github.com/davecgh/go-spew/spew" + "github.com/peterbourgon/ff/v3/ffcli" ) func main() { - cfg := server.DefaultConfig() - var level, tag string - flag.StringVar(&level, "l", "DEBUG", "log level") // TODO(kyle): change this warning later - flag.IntVar(&cfg.Port, "p", cfg.Port, "port to listen on") - flag.StringVar(&tag, "t", "kdhcpd", "logging tag") + var configPath string + + flag.StringVar(&configPath, "f", "kdhcpd.yaml", "path to `config file`") flag.Parse() - log.Setup(level, tag) + root := &ffcli.Command{ + Exec: func(ctx context.Context, args []string) error { + cfg, err := config.Load(configPath) + if err != nil { + log.Fatal(err) + } - srv, err := server.NewServer(cfg) - if err != nil { - log.Fatal(err) + log.Debugf("read configuration file with version=%d", cfg.Version) + spew.Dump(*cfg) + return nil + }, } - for { - packet, err := srv.ReadFrom() - if err != nil { - log.Warning(err) - continue - } - - log.Debugf("receive %d byte packet from %s", len(packet.Data), packet.Addr) - spew.Dump(packet.Data) - } + root.ParseAndRun(context.Background(), os.Args[1:]) } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..888b841 --- /dev/null +++ b/config/config.go @@ -0,0 +1,147 @@ +package config + +import ( + "fmt" + "io/ioutil" + "net" + + "git.wntrmute.dev/kyle/kdhcp/log" + "gopkg.in/yaml.v2" +) + +const ( + CurrentVersion = 1 +) + +func ensureV4(ip net.IP) (net.IP, error) { + ip4 := ip.To4() + if ip4 == nil { + return ip4, fmt.Errorf("%s isn't an IPv4 address", ip) + } + + return ip4, nil +} + +type IPRange struct { + Start net.IP + End net.IP +} + +func (r *IPRange) ensureV4() (err error) { + if r.Start, err = ensureV4(r.Start); err != nil { + return fmt.Errorf("config: range start address %w", err) + } + + if r.End, err = ensureV4(r.End); err != nil { + return fmt.Errorf("config: range end address %w", err) + } + + return nil +} + +type Network struct { + Gateway net.IP `yaml:"gateway"` + Mask net.IP `yaml:"mask"` + Broadcast net.IP `yaml:"broadcast"` + DNS []net.IP `yaml:"dns"` + Domain string `yaml:"domain"` +} + +func (n *Network) ensureV4() (err error) { + n.Gateway, err = ensureV4(n.Gateway) + if err != nil { + return fmt.Errorf("config: gateway %w", err) + } + + n.Mask, err = ensureV4(n.Mask) + if err != nil { + return fmt.Errorf("config: mask %w", err) + } + + n.Broadcast, err = ensureV4(n.Broadcast) + if err != nil { + return fmt.Errorf("config: broadcast %w", err) + } + + for i := range n.DNS { + n.DNS[i], err = ensureV4(n.DNS[i]) + if err != nil { + return fmt.Errorf("config: DNS address %w", err) + } + } + + return nil +} + +type ConfigFile struct { + Server *Config `yaml:"kdhcp"` +} + +type Config struct { + Version int `yaml:"version"` + Interface string `yaml:"interface"` + LeaseFile string `yaml:"lease_file"` + Network *Network `yaml:"network"` + Pools map[string]*IPRange `yaml:"pools"` + Statics map[string]net.IP `yaml:"statics"` + Device *net.Interface +} + +func (cfg *Config) process() (err error) { + switch { + case cfg.Version == 0: + log.Warningln("config: Version is 0, which indicates it hasn't been set. The config may be invalid.") + case cfg.Version > CurrentVersion: + log.Warningf("config: Version is greater than the current version %d. The config may not behave as expected.", CurrentVersion) + } + + cfg.Device, err = net.InterfaceByName(cfg.Interface) + if err != nil { + return fmt.Errorf("config: while looking up interface %s: %w", cfg.Interface, err) + } + + err = cfg.Network.ensureV4() + if err != nil { + return err + } + + for k, v := range cfg.Pools { + if err = v.ensureV4(); err != nil { + return fmt.Errorf("config: pool %s %w", k, err) + } + + cfg.Pools[k] = v + } + + for k, v := range cfg.Statics { + cfg.Statics[k], err = ensureV4(v) + if err != nil { + return fmt.Errorf("config: %s %w", k, err) + } + } + + return nil +} + +func Load(path string) (*Config, error) { + data, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("config: loading %s: %w", path, err) + } + + configFile := &ConfigFile{} + err = yaml.Unmarshal(data, configFile) + if err != nil { + return nil, fmt.Errorf("config: while unmarshaling %s: %w", path, err) + } + + if configFile.Server == nil { + log.Fatal("missing `kdhcp` section of config") + } + config := configFile.Server + if err = config.process(); err != nil { + return nil, err + } + + return config, nil +} diff --git a/go.mod b/go.mod index cef7529..4450778 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,7 @@ go 1.20 require github.com/hashicorp/go-syslog v1.0.0 require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 // indirect - github.com/josharian/native v1.1.0 // indirect - github.com/pierrec/lz4/v4 v4.1.14 // indirect - github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect - golang.org/x/sys v0.5.0 // indirect + github.com/davecgh/go-spew v1.1.1 + github.com/peterbourgon/ff/v3 v3.3.0 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1e30595..a74ed97 100644 --- a/go.sum +++ b/go.sum @@ -2,15 +2,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E= -github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI= -github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= -github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= -github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= -github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/peterbourgon/ff/v3 v3.3.0 h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24= +github.com/peterbourgon/ff/v3 v3.3.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/kdhcpd.yaml b/kdhcpd.yaml new file mode 100644 index 0000000..4297821 --- /dev/null +++ b/kdhcpd.yaml @@ -0,0 +1,27 @@ +kdhcp: + version: 1 + lease_file: /tmp/kdhcp_lease.yaml + # interface: enp89s0 + interface: wlp166s0 + network: + gateway: 192.168.4.254 + mask: 255.255.255.0 + broadcast: 192.168.4.255 + dns: + - 1.1.1.1 + - 8.8.8.8 + domain: cluster.wntrmute.dev + pools: + cluster: + start: 192.168.4.1 + end: 192.168.4.32 + controller: + start: 192.168.4.64 + end: 192.168.4.68 + default: + start: 192.168.4.128 + end: 192.168.4.164 + statics: + controller: 192.168.4.254 + haven: 192.168.4.253 + build01: 192.168.4.252 diff --git a/server/BUILD.bazel b/server/BUILD.bazel deleted file mode 100644 index 7cf6ab0..0000000 --- a/server/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "server", - srcs = [ - "config.go", - "packet.go", - "server.go", - ], - importpath = "git.wntrmute.dev/kyle/kdhcp/server", - visibility = ["//visibility:public"], - deps = ["//log"], -) diff --git a/server/config.go b/server/config.go deleted file mode 100644 index 782f532..0000000 --- a/server/config.go +++ /dev/null @@ -1,19 +0,0 @@ -package server - -const ( - DefaultPort = 67 - DefaultMaxPacketSize = 512 - DefaultNetwork = "udp4" -) - -type Config struct { - Port int - MaxPacketSize int -} - -func DefaultConfig() *Config { - return &Config{ - Port: DefaultPort, - MaxPacketSize: DefaultMaxPacketSize, - } -} diff --git a/server/packet.go b/server/packet.go deleted file mode 100644 index ad5451b..0000000 --- a/server/packet.go +++ /dev/null @@ -1,14 +0,0 @@ -package server - -import ( - "net" - - "github.com/insomniacslk/dhcp/dhcpv4" -) - -type Packet struct { - Data []byte - Addr net.Addr - - Raw *dhcpv4.DHCPv4 -} diff --git a/server/server.go b/server/server.go deleted file mode 100644 index dcd2503..0000000 --- a/server/server.go +++ /dev/null @@ -1,67 +0,0 @@ -package server - -import ( - "fmt" - "net" - - "git.wntrmute.dev/kyle/kdhcp/log" - "github.com/insomniacslk/dhcp/dhcpv4" -) - -type Server struct { - cfg *Config - conn net.PacketConn -} - -func (srv *Server) Listen() (err error) { - if srv.conn != nil { - srv.conn.Close() - } - - log.Debugf("attempting to set up packet listener on %s 0.0.0.0:%d", DefaultNetwork, srv.cfg.Port) - srv.conn, err = net.ListenPacket(DefaultNetwork, fmt.Sprintf(":%d", srv.cfg.Port)) - if err != nil { - return err - } - - return nil -} - -func (srv *Server) Close() error { - if srv.conn != nil { - return srv.conn.Close() - } - return nil -} - -func (srv *Server) ReadFrom() (*Packet, error) { - b := make([]byte, srv.cfg.MaxPacketSize) - n, addr, err := srv.conn.ReadFrom(b) - if err != nil { - return nil, err - } - - packet := &Packet{ - Data: b[:n], - Addr: addr, - } - packet.Raw, err = dhcpv4.FromBytes(b[:n]) - if err != nil { - return packet, err - } - - return packet, nil -} - -func NewServer(cfg *Config) (*Server, error) { - srv := &Server{ - cfg: cfg, - } - - err := srv.Listen() - if err != nil { - return nil, err - } - - return srv, nil -}