dhcp reads done, moving on to offers

This commit is contained in:
Kyle Isom 2023-05-09 07:28:54 +00:00
parent 6ba2bf3911
commit f66fbc0f6c
20 changed files with 196 additions and 58 deletions

View File

@ -9,7 +9,7 @@ go_library(
"//config",
"//server",
"@com_github_peterbourgon_ff_v3//ffcli",
"@dev_wntrmute_git_kyle_goutils//syslog",
"@dev_wntrmute_git_kyle_goutils//log",
],
)

View File

@ -6,7 +6,7 @@ import (
"os"
"strings"
log "git.wntrmute.dev/kyle/goutils/syslog"
"git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/config"
"git.wntrmute.dev/kyle/kdhcp/server"
"github.com/peterbourgon/ff/v3/ffcli"

View File

@ -10,7 +10,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//iptools",
"@dev_wntrmute_git_kyle_goutils//syslog",
"@dev_wntrmute_git_kyle_goutils//log",
"@in_gopkg_yaml_v2//:yaml_v2",
],
)

View File

@ -6,7 +6,7 @@ import (
"io/ioutil"
"net"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/iptools"
"gopkg.in/yaml.v2"
)

View File

@ -5,7 +5,7 @@ import (
"os/user"
"path/filepath"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
)
func FindConfigPath() string {

View File

@ -1,6 +1,13 @@
load("@bazel_gazelle//:deps.bzl", "go_repository")
def go_dependencies():
go_repository(
name = "com_github_benbjohnson_clock",
importpath = "github.com/benbjohnson/clock",
sum = "h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc=",
version = "v1.3.3",
)
go_repository(
name = "com_github_cloudflare_cfssl",
importpath = "github.com/cloudflare/cfssl",
@ -14,12 +21,32 @@ def go_dependencies():
sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=",
version = "v1.1.1",
)
go_repository(
name = "com_github_google_certificate_transparency_go",
importpath = "github.com/google/certificate-transparency-go",
sum = "h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=",
version = "v1.0.21",
)
go_repository(
name = "com_github_hashicorp_go_syslog",
importpath = "github.com/hashicorp/go-syslog",
sum = "h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE=",
version = "v1.0.0",
)
go_repository(
name = "com_github_kr_fs",
importpath = "github.com/kr/fs",
sum = "h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=",
version = "v0.1.0",
)
go_repository(
name = "com_github_kr_pretty",
importpath = "github.com/kr/pretty",
sum = "h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=",
version = "v0.1.0",
)
go_repository(
name = "com_github_kr_text",
importpath = "github.com/kr/text",
@ -51,6 +78,13 @@ def go_dependencies():
sum = "h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=",
version = "v1.12.0",
)
go_repository(
name = "com_github_pmezard_go_difflib",
importpath = "github.com/pmezard/go-difflib",
sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
version = "v1.0.0",
)
go_repository(
name = "com_github_stretchr_testify",
importpath = "github.com/stretchr/testify",
@ -60,15 +94,15 @@ def go_dependencies():
go_repository(
name = "dev_wntrmute_git_kyle_goutils",
importpath = "git.wntrmute.dev/kyle/goutils",
sum = "h1:CRCBlmSXOTkShbqC6j9lgxh4lb+khzc2zpIJYGQJtnc=",
version = "v1.6.6",
sum = "h1:+lk6uUMcpJK49sEGEMCOns3WVd2ThH/htMWnsyXEGl8=",
version = "v1.7.0",
)
go_repository(
name = "in_gopkg_check_v1",
importpath = "gopkg.in/check.v1",
sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
version = "v0.0.0-20161208181325-20d25e280405",
sum = "h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=",
version = "v1.0.0-20180628173108-788fd7840127",
)
go_repository(
name = "in_gopkg_yaml_v2",
@ -76,6 +110,13 @@ def go_dependencies():
sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=",
version = "v2.4.0",
)
go_repository(
name = "in_gopkg_yaml_v3",
importpath = "gopkg.in/yaml.v3",
sum = "h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=",
version = "v3.0.0-20200313102051-9f266ea9e77c",
)
go_repository(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",

View File

@ -9,5 +9,5 @@ go_library(
],
importpath = "git.wntrmute.dev/kyle/kdhcp/dhcp",
visibility = ["//visibility:public"],
deps = ["@dev_wntrmute_git_kyle_goutils//syslog"],
deps = ["@dev_wntrmute_git_kyle_goutils//log"],
)

View File

@ -33,6 +33,8 @@ const (
OptionTagNBScope OptionTag = 47
OptionTagMessageType OptionTag = 53
OptionTagParameterRequestList OptionTag = 55
OptionTagDHCPMaxMessageSize OptionTag = 57
OptionTagClientID OptionTag = 61
OptionTagDomainSearch OptionTag = 119
OptionTagClasslessStaticRoute OptionTag = 121
OptionTagEnd OptionTag = 255
@ -53,6 +55,8 @@ var optionStrings = map[OptionTag]string{
OptionTagNBScope: "NetBIOS scope",
OptionTagMessageType: "message type",
OptionTagParameterRequestList: "parameter request list",
OptionTagDHCPMaxMessageSize: "DHCP max message size",
OptionTagClientID: "client ID",
OptionTagDomainSearch: "search domain",
OptionTagClasslessStaticRoute: "classless static route",
OptionTagEnd: "end",

View File

@ -8,7 +8,7 @@ import (
"net"
"strings"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
)
const (
@ -46,6 +46,7 @@ type BootRequest struct {
DHCPType DHCPMessageType // option 53
HostName string // option 12
ClientID string // option 61
ParameterRequests []OptionTag
endOptions bool
}

View File

@ -6,7 +6,7 @@ import (
"fmt"
"io"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
)
var optionRegistry = map[OptionTag]Option{
@ -14,6 +14,7 @@ var optionRegistry = map[OptionTag]Option{
OptionTagHostName: OptionHostName,
OptionTagMessageType: OptionMessageType,
OptionTagParameterRequestList: OptionParameterRequestList,
OptionTagClientID: OptionClientID,
OptionTagEnd: OptionEnd,
}
@ -22,6 +23,19 @@ func OptionPad(req *BootRequest, r io.Reader) error {
return nil
}
func getOptionLength(r io.Reader) (int, error) {
var length uint8
read := newPacketReaderFunc(r)
if err := read(&length); err != nil {
return -1, fmt.Errorf("dhcp: reading option length for DHCP Message Type")
} else if length == 0 {
return -1, errors.New("dhcp: read option length 0, but expected option length for DHCP Host Name is >= 1")
}
return int(length), nil
}
// OptionHostName reads a DHCP host name option.
//
// 3.14. Host Name Option
@ -33,13 +47,9 @@ func OptionPad(req *BootRequest, r io.Reader) error {
// The code for this option is 12, and its minimum length is 1.
func OptionHostName(req *BootRequest, r io.Reader) error {
read := newPacketReaderFunc(r)
var length uint8
if err := read(&length); err != nil {
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
} else if length == 0 {
return errors.New("dhcp: read option length 0, but expected option length for DHCP Host Name is >= 1")
length, err := getOptionLength(r)
if err != nil {
return err
}
hostName := make([]byte, int(length))
@ -56,9 +66,8 @@ func OptionHostName(req *BootRequest, r io.Reader) error {
func OptionMessageType(req *BootRequest, r io.Reader) error {
read := newPacketReaderFunc(r)
var length uint8
if err := read(&length); err != nil {
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
if length, err := getOptionLength(r); err != nil {
return err
} else if length != 1 {
return fmt.Errorf("dhcp: read option length %d, but expected option length for DHCP Message Type is 1", length)
}
@ -71,11 +80,9 @@ func OptionMessageType(req *BootRequest, r io.Reader) error {
}
func OptionParameterRequestList(req *BootRequest, r io.Reader) error {
read := newPacketReaderFunc(r)
var length uint8
if err := read(&length); err != nil {
return fmt.Errorf("dhcp: reading option length for DHCP Message Type")
length, err := getOptionLength(r)
if err != nil {
return err
} else if length == 0 {
return fmt.Errorf("dhcp: read option length %d, but expected option length for DHCP Parameter Request is >= 1", length)
}
@ -96,6 +103,25 @@ func OptionParameterRequestList(req *BootRequest, r io.Reader) error {
return nil
}
func OptionClientID(req *BootRequest, r io.Reader) error {
length, err := getOptionLength(r)
if err != nil {
return err
} else if length == 0 {
return fmt.Errorf("dhcp: read option length %d, but expected option length for DHCP Parameter Request is >= 1", length)
}
var clientID = make([]byte, int(length))
if n, err := r.Read(clientID); err != nil {
return fmt.Errorf("dhcp: while reading client ID: %w", err)
} else if n != int(length) {
return fmt.Errorf("dhcp: only read %d bytes of client ID, expected %d bytes", n, length)
}
req.ClientID = string(clientID)
return nil
}
func OptionEnd(req *BootRequest, r io.Reader) error {
req.endOptions = true
return nil
@ -108,7 +134,27 @@ func ReadOption(req *BootRequest, tag byte, r io.Reader) error {
return f(req, r)
}
return fmt.Errorf("dhcp: unknown/unhandled option %d", opt)
return readUnknownOption(req, tag, r)
}
func readUnknownOption(req *BootRequest, tag byte, r io.Reader) error {
length, err := getOptionLength(r)
if err != nil {
return err
} else if length == 0 {
log.Debugf("skipped option %d/%02x with length 0", tag, tag)
return nil
}
var data = make([]byte, length)
if n, err := r.Read(data); err != nil {
return fmt.Errorf("dhcp: while skipping unknown tag %d/%02x: %w", tag, tag, err)
} else if n != int(length) {
return fmt.Errorf("dhcp: only read %d bytes of unknown tag %d/%02x, expected %d bytes", n, tag, tag, length)
}
log.Infof("skipped unknown tag %d/%02x with data %0x", tag, tag, data)
return nil
}
const magicCookieLength = 4

10
go.mod
View File

@ -2,12 +2,16 @@ module git.wntrmute.dev/kyle/kdhcp
go 1.20
require github.com/hashicorp/go-syslog v1.0.0
require github.com/hashicorp/go-syslog v1.0.0 // indirect
require (
github.com/davecgh/go-spew v1.1.1
github.com/peterbourgon/ff/v3 v3.3.0
gopkg.in/yaml.v2 v2.4.0
)
require git.wntrmute.dev/kyle/goutils v1.6.6 // indirect
require github.com/davecgh/go-spew v1.1.1 // indirect
require (
git.wntrmute.dev/kyle/goutils v1.7.0
github.com/benbjohnson/clock v1.3.3
)

10
go.sum
View File

@ -1,12 +1,16 @@
git.wntrmute.dev/kyle/goutils v1.6.6 h1:CRCBlmSXOTkShbqC6j9lgxh4lb+khzc2zpIJYGQJtnc=
git.wntrmute.dev/kyle/goutils v1.6.6/go.mod h1:p0m2YprqMXkqtxTPKCiRcmgYo/D/9DtAIRfNVFE3JBg=
git.wntrmute.dev/kyle/goutils v1.7.0 h1:+lk6uUMcpJK49sEGEMCOns3WVd2ThH/htMWnsyXEGl8=
git.wntrmute.dev/kyle/goutils v1.7.0/go.mod h1:hMcPr+XSYXjQ/IRTziNVYmUmb9BPATZc+cyehSjBs0w=
github.com/benbjohnson/clock v1.3.3 h1:g+rSsSaAzhHJYcIQE78hJ3AhyjjtQvleKDjlhdBnIhc=
github.com/benbjohnson/clock v1.3.3/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -11,7 +11,10 @@ go_library(
],
importpath = "git.wntrmute.dev/kyle/kdhcp/iptools",
visibility = ["//visibility:public"],
deps = ["@dev_wntrmute_git_kyle_goutils//assert"],
deps = [
"@dev_wntrmute_git_kyle_goutils//assert",
"@dev_wntrmute_git_kyle_goutils//log",
],
)
go_test(

View File

@ -15,6 +15,10 @@ type LeaseInfo struct {
Expires time.Time `yaml:"expires"`
}
func (li *LeaseInfo) String() string {
return fmt.Sprintf("lease[hostname=%s addr=%s hw=%x expires=%s]", li.HostName, li.Addr, li.HardwareAddress, li.Expires)
}
type sortableLease []*LeaseInfo
func (a sortableLease) Len() int { return len(a) }

View File

@ -7,6 +7,7 @@ import (
"time"
"git.wntrmute.dev/kyle/goutils/assert"
"git.wntrmute.dev/kyle/goutils/log"
)
const DefaultExpiry = 168 * time.Hour
@ -23,7 +24,7 @@ type Pool struct {
}
func (p *Pool) lock() {
if p.lock == nil {
if p.mtx == nil {
p.mtx = &sync.Mutex{}
}
@ -48,6 +49,7 @@ func NewPool(name string, r *Range) (*Pool, error) {
Expiry: r.Expiry,
NoHostName: r.NoHostName,
Available: enumerateRange(name, r, true),
Limbo: map[netip.Addr]*LeaseInfo{},
}
return p, nil
@ -114,39 +116,40 @@ func (p *Pool) Accept(addr netip.Addr, t time.Time) error {
// Update checks expirations on leases, moving any expired leases back
// to the available pool and removing any limbo leases that are expired.
func (p *Pool) Update(t time.Time) {
func (p *Pool) Update(t time.Time) bool {
p.lock()
defer p.unlock()
p.update(t)
return p.update(t)
}
func (p *Pool) update(t time.Time) {
evictedHosts := []netip.Addr{}
func (p *Pool) update(t time.Time) bool {
var updated bool
for k, v := range p.Active {
if v.IsExpired(t) {
evictedHosts = append(evictedHosts, k)
updated = true
log.Infof("expiring active address %s for %x", v.Addr, v.HardwareAddress)
delete(p.Active, k)
v = v.Reset()
p.Available = append(p.Available, v)
}
}
for _, ip := range evictedHosts {
delete(p.Active, ip)
}
for k, v := range p.Limbo {
if v.IsExpired(t) {
updated = true
log.Infof("expiring limbo address %s for %x", v.Addr, v.HardwareAddress)
delete(p.Limbo, k)
v = v.Reset()
p.Available = append(p.Available, v)
}
}
p.Sort()
p.sort()
return updated
}
func (p *Pool) Renew(lease LeaseInfo, t time.Time) {
func (p *Pool) Renew(lease *LeaseInfo, t time.Time) {
p.Active[lease.Addr].ResetExpiry(t, p.Expiry)
}

View File

@ -26,4 +26,4 @@ kdhcp:
statics:
controller: 192.168.4.254
haven: 192.168.4.253
build01: 192.168.4.252
build01: 192.168.4.252

View File

@ -15,7 +15,8 @@ go_library(
"//config",
"//dhcp",
"//iptools",
"@dev_wntrmute_git_kyle_goutils//syslog",
"@com_github_benbjohnson_clock//:clock",
"@dev_wntrmute_git_kyle_goutils//log",
"@in_gopkg_yaml_v2//:yaml_v2",
],
)

View File

@ -4,7 +4,7 @@ import (
"fmt"
"os"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/iptools"
"gopkg.in/yaml.v2"
)

View File

@ -3,8 +3,9 @@ package server
import (
"fmt"
"net/netip"
"time"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/iptools"
)
@ -35,3 +36,14 @@ func (srv *Server) loadPoolsFromConfig() error {
return nil
}
func (srv *Server) updatePoolLoop() {
for {
time.Sleep(time.Minute)
for _, p := range srv.Pools {
if p.Update(srv.clock.Now()) {
log.Debugln("pools updated")
}
}
}
}

View File

@ -5,10 +5,11 @@ import (
"net"
"time"
log "git.wntrmute.dev/kyle/goutils/syslog"
log "git.wntrmute.dev/kyle/goutils/log"
"git.wntrmute.dev/kyle/kdhcp/config"
"git.wntrmute.dev/kyle/kdhcp/dhcp"
"git.wntrmute.dev/kyle/kdhcp/iptools"
"github.com/benbjohnson/clock"
)
const (
@ -22,6 +23,8 @@ type Server struct {
Config *config.Config
Pools map[string]*iptools.Pool
Static map[string]*iptools.LeaseInfo
clock clock.Clock
}
func (s *Server) Close() error {
@ -62,23 +65,34 @@ func (s *Server) WriteTo(b []byte, addr net.Addr) error {
return errors.New("server: not implemented")
}
func (s *Server) AcceptPacket() error {
func (s *Server) AcceptPacket() (*dhcp.BootRequest, error) {
request, err := s.ReadDHCPRequest()
if err != nil {
return err
return nil, err
}
log.Debugf("BOOTP request received from %x", request.HardwareAddress)
return nil
return request, nil
}
func (s *Server) Listen() {
go s.updatePoolLoop()
for {
if err := s.AcceptPacket(); err != nil {
req, err := s.AcceptPacket()
if err != nil {
log.Errf("server: error reading packet: %s", err)
continue
}
break
lease := s.SelectLease(req, s.clock.Now())
if err != nil {
log.Err("server: couldn't find available lease")
continue
}
log.Infof("available lease: %s", lease)
continue
}
}
@ -87,6 +101,7 @@ func New(cfg *config.Config) (*Server, error) {
Config: cfg,
Pools: map[string]*iptools.Pool{},
Static: map[string]*iptools.LeaseInfo{},
clock: clock.New(),
}
if err := srv.loadPoolsFromConfig(); err != nil {