basic UDP server bound to an interface

This commit is contained in:
Kyle Isom 2023-05-02 09:27:27 -07:00
parent cb8e6c031c
commit 073dbac4bb
10 changed files with 264 additions and 9 deletions

View File

@ -6,8 +6,10 @@ go_library(
importpath = "git.wntrmute.dev/kyle/kdhcp/cmd/kdhcpd", importpath = "git.wntrmute.dev/kyle/kdhcp/cmd/kdhcpd",
visibility = ["//visibility:private"], visibility = ["//visibility:private"],
deps = [ deps = [
"//config",
"//log", "//log",
"//server", "//server",
"@com_github_peterbourgon_ff_v3//ffcli",
], ],
) )

View File

@ -4,19 +4,25 @@ import (
"context" "context"
"flag" "flag"
"os" "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/config"
"git.wntrmute.dev/kyle/kdhcp/log" "git.wntrmute.dev/kyle/kdhcp/log"
"git.wntrmute.dev/kyle/kdhcp/server"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
) )
func main() { func main() {
var configPath string 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() flag.Parse()
logLevel = strings.ToUpper(logLevel)
log.Setup(logLevel, "kdhcpd")
root := &ffcli.Command{ root := &ffcli.Command{
Exec: func(ctx context.Context, args []string) error { Exec: func(ctx context.Context, args []string) error {
cfg, err := config.Load(configPath) cfg, err := config.Load(configPath)
@ -24,8 +30,15 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
log.Debugf("read configuration file with version=%d", cfg.Version) srv, err := server.New(cfg)
spew.Dump(*cfg) if err != nil {
log.Fatal(err)
}
defer srv.Close()
srv.Listen()
return nil return nil
}, },
} }

15
config/BUILD.bazel Normal file
View File

@ -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",
],
)

View File

@ -1,9 +1,11 @@
package config package config
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net" "net"
"strconv"
"git.wntrmute.dev/kyle/kdhcp/log" "git.wntrmute.dev/kyle/kdhcp/log"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -78,13 +80,15 @@ type ConfigFile struct {
} }
type Config struct { type Config struct {
Version int `yaml:"version"` Version int `yaml:"version"`
Interface string `yaml:"interface"` Interface string `yaml:"interface"`
Address string `yaml:"address"`
IP net.IP
Port int
LeaseFile string `yaml:"lease_file"` LeaseFile string `yaml:"lease_file"`
Network *Network `yaml:"network"` Network *Network `yaml:"network"`
Pools map[string]*IPRange `yaml:"pools"` Pools map[string]*IPRange `yaml:"pools"`
Statics map[string]net.IP `yaml:"statics"` Statics map[string]net.IP `yaml:"statics"`
Device *net.Interface
} }
func (cfg *Config) process() (err error) { 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) 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 { if err != nil {
return fmt.Errorf("config: while looking up interface %s: %w", cfg.Interface, err) 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() err = cfg.Network.ensureV4()
if err != nil { if err != nil {
return err return err
@ -124,6 +148,14 @@ func (cfg *Config) process() (err error) {
} }
func Load(path string) (*Config, 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) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("config: loading %s: %w", path, err) return nil, fmt.Errorf("config: loading %s: %w", path, err)
@ -143,5 +175,6 @@ func Load(path string) (*Config, error) {
return nil, err return nil, err
} }
log.Debugf("config: read configuration from %s", path)
return config, nil return config, nil
} }

37
config/path.go Normal file
View File

@ -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 ""
}

View File

@ -13,3 +13,27 @@ def go_dependencies():
sum = "h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=", sum = "h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=",
version = "v1.0.0", 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",
)

View File

@ -2,7 +2,8 @@ kdhcp:
version: 1 version: 1
lease_file: /tmp/kdhcp_lease.yaml lease_file: /tmp/kdhcp_lease.yaml
# interface: enp89s0 # interface: enp89s0
interface: wlp166s0 interface: eth0
address: 192.168.4.250:67
network: network:
gateway: 192.168.4.254 gateway: 192.168.4.254
mask: 255.255.255.0 mask: 255.255.255.0

15
server/BUILD.bazel Normal file
View File

@ -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",
],
)

39
server/ifi.go Normal file
View File

@ -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
}

76
server/server.go Normal file
View File

@ -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
}