diff --git a/cmd/kdhcpd/BUILD.bazel b/cmd/kdhcpd/BUILD.bazel index 9ffd6e3..09dbf74 100644 --- a/cmd/kdhcpd/BUILD.bazel +++ b/cmd/kdhcpd/BUILD.bazel @@ -6,8 +6,10 @@ go_library( importpath = "git.wntrmute.dev/kyle/kdhcp/cmd/kdhcpd", visibility = ["//visibility:private"], deps = [ + "//config", "//log", "//server", + "@com_github_peterbourgon_ff_v3//ffcli", ], ) diff --git a/cmd/kdhcpd/main.go b/cmd/kdhcpd/main.go index 4db973f..c51c940 100644 --- a/cmd/kdhcpd/main.go +++ b/cmd/kdhcpd/main.go @@ -4,19 +4,25 @@ import ( "context" "flag" "os" + "strings" - "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/peterbourgon/ff/v3/ffcli" ) func main() { var configPath string + logLevel := "DEBUG" - flag.StringVar(&configPath, "f", "kdhcpd.yaml", "path to `config file`") + flag.StringVar(&configPath, "f", "", "path to `config file`") + flag.StringVar(&logLevel, "l", logLevel, "log level") flag.Parse() + logLevel = strings.ToUpper(logLevel) + log.Setup(logLevel, "kdhcpd") + root := &ffcli.Command{ Exec: func(ctx context.Context, args []string) error { cfg, err := config.Load(configPath) @@ -24,8 +30,15 @@ func main() { log.Fatal(err) } - log.Debugf("read configuration file with version=%d", cfg.Version) - spew.Dump(*cfg) + srv, err := server.New(cfg) + if err != nil { + log.Fatal(err) + } + + defer srv.Close() + + srv.Listen() + return nil }, } diff --git a/config/BUILD.bazel b/config/BUILD.bazel new file mode 100644 index 0000000..d66b6eb --- /dev/null +++ b/config/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = [ + "config.go", + "path.go", + ], + importpath = "git.wntrmute.dev/kyle/kdhcp/config", + visibility = ["//visibility:public"], + deps = [ + "//log", + "@in_gopkg_yaml_v2//:yaml_v2", + ], +) diff --git a/config/config.go b/config/config.go index 888b841..baac7a5 100644 --- a/config/config.go +++ b/config/config.go @@ -1,9 +1,11 @@ package config import ( + "errors" "fmt" "io/ioutil" "net" + "strconv" "git.wntrmute.dev/kyle/kdhcp/log" "gopkg.in/yaml.v2" @@ -78,13 +80,15 @@ type ConfigFile struct { } type Config struct { - Version int `yaml:"version"` - Interface string `yaml:"interface"` + Version int `yaml:"version"` + Interface string `yaml:"interface"` + Address string `yaml:"address"` + IP net.IP + Port int 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) { @@ -95,11 +99,31 @@ func (cfg *Config) process() (err error) { 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) + _, err = net.InterfaceByName(cfg.Interface) if err != nil { return fmt.Errorf("config: while looking up interface %s: %w", cfg.Interface, err) } + ip, port, err := net.SplitHostPort(cfg.Address) + if err != nil { + return err + } + + cfg.IP = net.ParseIP(ip) + if cfg.IP == nil { + return fmt.Errorf("config: parsing IP from address %s: %w", cfg.Address, err) + } + + cfg.IP, err = ensureV4(cfg.IP) + if err != nil { + return fmt.Errorf("config: address %w", err) + } + + cfg.Port, err = strconv.Atoi(port) + if err != nil { + return fmt.Errorf("config: invalid port %s: %w", port, err) + } + err = cfg.Network.ensureV4() if err != nil { return err @@ -124,6 +148,14 @@ func (cfg *Config) process() (err error) { } func Load(path string) (*Config, error) { + if path == "" { + path = FindConfigPath() + } + + if path == "" { + return nil, errors.New("config: no config file path specified and couldn't find a valid config file path") + } + data, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf("config: loading %s: %w", path, err) @@ -143,5 +175,6 @@ func Load(path string) (*Config, error) { return nil, err } + log.Debugf("config: read configuration from %s", path) return config, nil } diff --git a/config/path.go b/config/path.go new file mode 100644 index 0000000..e0c42e9 --- /dev/null +++ b/config/path.go @@ -0,0 +1,37 @@ +package config + +import ( + "os" + "os/user" + "path/filepath" + + "git.wntrmute.dev/kyle/kdhcp/log" +) + +func FindConfigPath() string { + paths := []string{"/home/kyle/code/kdhcp/kdhcpd.yaml"} + user, err := user.Current() + if err == nil { + if homedir := user.HomeDir; homedir != "" { + paths = append(paths, filepath.Join(homedir, ".config", "kdhcp", "kdhcpd.yaml")) + } + } + + paths = append(paths, "/etc/kdhcp/kdhcpd.yaml") + + for _, path := range paths { + _, err = os.Stat(path) + if os.IsNotExist(err) { + continue + } + + if err != nil { + log.Debugf("config: while trying to stat config file at '%s': %s", path, err) + continue + } + + return path + } + + return "" +} diff --git a/deps.bzl b/deps.bzl index 07450d8..888261c 100644 --- a/deps.bzl +++ b/deps.bzl @@ -13,3 +13,27 @@ def go_dependencies(): sum = "h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=", version = "v1.0.0", ) + go_repository( + name = "com_github_pelletier_go_toml", + importpath = "github.com/pelletier/go-toml", + sum = "h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=", + version = "v1.9.5", + ) + go_repository( + name = "com_github_peterbourgon_ff_v3", + importpath = "github.com/peterbourgon/ff/v3", + sum = "h1:PaKe7GW8orVFh8Unb5jNHS+JZBwWUMa2se0HM6/BI24=", + version = "v3.3.0", + ) + go_repository( + name = "in_gopkg_check_v1", + importpath = "gopkg.in/check.v1", + sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=", + version = "v0.0.0-20161208181325-20d25e280405", + ) + go_repository( + name = "in_gopkg_yaml_v2", + importpath = "gopkg.in/yaml.v2", + sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=", + version = "v2.4.0", + ) diff --git a/kdhcpd.yaml b/kdhcpd.yaml index 4297821..3cd1f77 100644 --- a/kdhcpd.yaml +++ b/kdhcpd.yaml @@ -2,7 +2,8 @@ kdhcp: version: 1 lease_file: /tmp/kdhcp_lease.yaml # interface: enp89s0 - interface: wlp166s0 + interface: eth0 + address: 192.168.4.250:67 network: gateway: 192.168.4.254 mask: 255.255.255.0 diff --git a/server/BUILD.bazel b/server/BUILD.bazel new file mode 100644 index 0000000..d21f781 --- /dev/null +++ b/server/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "server", + srcs = [ + "ifi.go", + "server.go", + ], + importpath = "git.wntrmute.dev/kyle/kdhcp/server", + visibility = ["//visibility:public"], + deps = [ + "//config", + "//log", + ], +) diff --git a/server/ifi.go b/server/ifi.go new file mode 100644 index 0000000..b70f388 --- /dev/null +++ b/server/ifi.go @@ -0,0 +1,39 @@ +package server + +import ( + "net" + "syscall" +) + +func BindInterface(ip net.IP, port int, dev string) (net.PacketConn, error) { + if port == 0 { + port = 67 + } + + udpAddr := &net.UDPAddr{ + IP: ip, + Port: port, + } + + conn, err := net.ListenUDP("udp4", udpAddr) + if err != nil { + return nil, err + } + + sysconn, err := conn.SyscallConn() + if err != nil { + conn.Close() + return nil, err + } + + sysconn.Control(func(fd uintptr) { + err = syscall.BindToDevice(int(fd), dev) + }) + + if err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..0fc40ff --- /dev/null +++ b/server/server.go @@ -0,0 +1,76 @@ +package server + +import ( + "errors" + "net" + + "git.wntrmute.dev/kyle/kdhcp/config" + "git.wntrmute.dev/kyle/kdhcp/log" +) + +const ( + MaxPacketSize = 512 +) + +type Server struct { + Conn net.PacketConn + Config *config.Config +} + +func (s *Server) Close() error { + return s.Conn.Close() +} + +func (s *Server) Bind() (err error) { + s.Conn, err = BindInterface(s.Config.IP, s.Config.Port, s.Config.Interface) + return err +} + +func (s *Server) ReadFrom() ([]byte, net.Addr, error) { + b := make([]byte, MaxPacketSize) + n, addr, err := s.Conn.ReadFrom(b) + if err != nil { + return nil, nil, err + } + + b = b[:n] + return b, addr, nil +} + +func (s *Server) WriteTo(b []byte, addr net.Addr) error { + return errors.New("server: not implemented") +} + +func (s *Server) AcceptPacket() error { + packet, addr, err := s.ReadFrom() + if err != nil { + return err + } + + log.Debugf("accepted %d byte packet from %s", len(packet), addr) + return nil +} + +func (s *Server) Listen() { + for { + if err := s.AcceptPacket(); err != nil { + log.Errf("server: error reading packet: %s", err) + continue + } + } +} + +func New(cfg *config.Config) (*Server, error) { + srv := &Server{ + Config: cfg, + } + + err := srv.Bind() + if err != nil { + return nil, err + } + + log.Infof("server: bound to %s:%s", cfg.Interface, srv.Conn.LocalAddr()) + + return srv, nil +}