basic UDP server bound to an interface
This commit is contained in:
parent
cb8e6c031c
commit
073dbac4bb
|
@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -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"
|
||||||
|
@ -80,11 +82,13 @@ 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ""
|
||||||
|
}
|
24
deps.bzl
24
deps.bzl
|
@ -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",
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue