working offers
This commit is contained in:
@@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
go_library(
|
||||
name = "server",
|
||||
srcs = [
|
||||
"dhcp_transaction.go",
|
||||
"ifi.go",
|
||||
"ifi_linux.go",
|
||||
"lease_state.go",
|
||||
@@ -15,6 +16,7 @@ go_library(
|
||||
"//config",
|
||||
"//dhcp",
|
||||
"//iptools",
|
||||
"//leases",
|
||||
"@com_github_benbjohnson_clock//:clock",
|
||||
"@dev_wntrmute_git_kyle_goutils//log",
|
||||
"@in_gopkg_yaml_v2//:yaml_v2",
|
||||
|
||||
58
server/dhcp_transaction.go
Normal file
58
server/dhcp_transaction.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
log "git.wntrmute.dev/kyle/goutils/log"
|
||||
"git.wntrmute.dev/kyle/kdhcp/dhcp"
|
||||
)
|
||||
|
||||
func (srv *Server) WriteDHCPResponse(req *dhcp.Packet, ip net.Addr) error {
|
||||
lease := srv.SelectLease(req, srv.clock.Now())
|
||||
if lease == nil {
|
||||
log.Errln("server: couldn't find available lease")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("available lease: %s", lease)
|
||||
response, err := dhcp.NewOffer(req, srv.Config.Network.NetworkInfo(), lease)
|
||||
if err != nil {
|
||||
log.Errln(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("building offer for address %s", lease.Addr)
|
||||
pkt, err := dhcp.WritePacket(response)
|
||||
if err != nil {
|
||||
log.Errln(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ip, err = addrReply(ip)
|
||||
if err != nil {
|
||||
log.Errln(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("writing offer to %s", ip)
|
||||
_, err = srv.Conn.WriteTo(pkt, ip)
|
||||
if err != nil {
|
||||
log.Errf("failed to write packet: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("sending offer for address %s to %s", lease.Addr, req.HardwareAddress)
|
||||
go func() {
|
||||
if err := srv.SaveLeases(); err != nil {
|
||||
log.Warningf("server: while saving leases: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = srv.AcceptLease(req, lease, srv.clock.Now())
|
||||
if err != nil {
|
||||
log.Errln(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,20 +3,20 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
log "git.wntrmute.dev/kyle/goutils/log"
|
||||
)
|
||||
|
||||
func BindInterface(ip net.IP, port int, dev string) (net.PacketConn, error) {
|
||||
if port == 0 {
|
||||
port = 67
|
||||
}
|
||||
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: ip,
|
||||
Port: port,
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("listen on %s", udpAddr)
|
||||
conn, err := net.ListenUDP("udp4", udpAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,62 +1,66 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
log "git.wntrmute.dev/kyle/goutils/log"
|
||||
"git.wntrmute.dev/kyle/kdhcp/iptools"
|
||||
"gopkg.in/yaml.v2"
|
||||
"git.wntrmute.dev/kyle/kdhcp/leases"
|
||||
)
|
||||
|
||||
type LeaseState struct {
|
||||
Pools map[string]*iptools.Pool `yaml:"pools"`
|
||||
Static map[string]*iptools.LeaseInfo `yaml:"static"`
|
||||
Pools map[string]*iptools.Pool `json:"pools"`
|
||||
Static map[string]*leases.Info `json:"static"`
|
||||
}
|
||||
|
||||
func (srv *Server) SaveLeases() error {
|
||||
leaseFile, err := os.Create(srv.Config.LeaseFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server: while saving leases: %w", err)
|
||||
}
|
||||
defer leaseFile.Close()
|
||||
encoder := yaml.NewEncoder(leaseFile)
|
||||
|
||||
state := &LeaseState{
|
||||
Pools: srv.Pools,
|
||||
Static: srv.Static,
|
||||
}
|
||||
|
||||
if err = encoder.Encode(state); err != nil {
|
||||
return fmt.Errorf("server: while saving leases: %w", err)
|
||||
out, err := json.MarshalIndent(state, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = encoder.Close(); err != nil {
|
||||
return fmt.Errorf("server: while saving leases: %w", err)
|
||||
err = os.WriteFile(srv.Config.LeaseFile, out, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("wrote lease state to file %s", srv.Config.LeaseFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) LoadLeases() error {
|
||||
leaseState := &LeaseState{}
|
||||
leaseFile, err := os.Open(srv.Config.LeaseFile)
|
||||
state := &LeaseState{}
|
||||
|
||||
stateBytes, err := os.ReadFile(srv.Config.LeaseFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Warningf("server: not loading leases from %s: lease file not found", srv.Config.LeaseFile)
|
||||
log.Infof("not restoring leases from %s: file doesn't exist", srv.Config.LeaseFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("server: while reading leases: %w", err)
|
||||
}
|
||||
defer leaseFile.Close()
|
||||
decoder := yaml.NewDecoder(leaseFile)
|
||||
|
||||
if err = decoder.Decode(leaseState); err != nil {
|
||||
return fmt.Errorf("server: while reading leases: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
srv.Pools = leaseState.Pools
|
||||
srv.Static = leaseState.Static
|
||||
err = json.Unmarshal(stateBytes, state)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *json.UnmarshalTypeError:
|
||||
log.Infof("type error: %q: [ \"bad type: got %s; want %s\" ]", err.Field, err.Value, err.Type.String())
|
||||
return err
|
||||
case *json.InvalidUnmarshalError:
|
||||
log.Infof("invalid unmarshal error: %s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("restored lease states")
|
||||
|
||||
srv.Pools = state.Pools
|
||||
srv.Static = state.Static
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
log "git.wntrmute.dev/kyle/goutils/log"
|
||||
"git.wntrmute.dev/kyle/kdhcp/iptools"
|
||||
"git.wntrmute.dev/kyle/kdhcp/leases"
|
||||
)
|
||||
|
||||
// pools.go adds pool functionality to the server.
|
||||
@@ -18,7 +19,7 @@ func (srv *Server) loadPoolsFromConfig() error {
|
||||
return fmt.Errorf("server: while instantiating pools, could not load IP %s", ip)
|
||||
}
|
||||
log.Debugf("server: added static host entry %s -> %s", host, addr)
|
||||
srv.Static[host] = &iptools.LeaseInfo{
|
||||
srv.Static[host] = &leases.Info{
|
||||
HostName: host,
|
||||
Addr: addr,
|
||||
}
|
||||
|
||||
105
server/server.go
105
server/server.go
@@ -1,7 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"git.wntrmute.dev/kyle/kdhcp/config"
|
||||
"git.wntrmute.dev/kyle/kdhcp/dhcp"
|
||||
"git.wntrmute.dev/kyle/kdhcp/iptools"
|
||||
"git.wntrmute.dev/kyle/kdhcp/leases"
|
||||
"github.com/benbjohnson/clock"
|
||||
)
|
||||
|
||||
@@ -18,11 +19,27 @@ const (
|
||||
MaxResponseWait = 5 * time.Minute
|
||||
)
|
||||
|
||||
func addrReply(addr net.Addr) (net.Addr, error) {
|
||||
udpAddr, err := net.ResolveUDPAddr("udp4", addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if udpAddr.IP.Equal(net.IPv4zero) {
|
||||
return &net.UDPAddr{
|
||||
IP: net.IPv4bcast,
|
||||
Port: udpAddr.Port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return udpAddr, nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Conn net.PacketConn
|
||||
Config *config.Config
|
||||
Pools map[string]*iptools.Pool
|
||||
Static map[string]*iptools.LeaseInfo
|
||||
Static map[string]*leases.Info
|
||||
|
||||
clock clock.Clock
|
||||
}
|
||||
@@ -51,48 +68,74 @@ func (s *Server) ReadFrom() ([]byte, net.Addr, error) {
|
||||
return b, addr, nil
|
||||
}
|
||||
|
||||
func (s *Server) ReadDHCPRequest() (*dhcp.BootRequest, error) {
|
||||
func (s *Server) ReadDHCPRequest() (*dhcp.Packet, net.Addr, error) {
|
||||
pkt, addr, err := s.ReadFrom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Debugf("server: read packet from %s", addr)
|
||||
return dhcp.ReadRequest(pkt)
|
||||
req, err := dhcp.ReadPacket(pkt)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return req, addr, nil
|
||||
}
|
||||
|
||||
func (s *Server) WriteTo(b []byte, addr net.Addr) error {
|
||||
return errors.New("server: not implemented")
|
||||
n, err := s.Conn.WriteTo(b, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("wrote %d bytes to %s", n, addr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) AcceptPacket() (*dhcp.BootRequest, error) {
|
||||
request, err := s.ReadDHCPRequest()
|
||||
func (s *Server) AcceptPacket() (*dhcp.Packet, net.Addr, error) {
|
||||
request, addr, err := s.ReadDHCPRequest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
log.Debugf("BOOTP request received from %x", request.HardwareAddress)
|
||||
return request, nil
|
||||
return request, addr, nil
|
||||
}
|
||||
|
||||
func (s *Server) Listen() {
|
||||
go s.updatePoolLoop()
|
||||
func (srv *Server) Listen() {
|
||||
go srv.updatePoolLoop()
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(5 * time.Minute)
|
||||
if err := srv.SaveLeases(); err != nil {
|
||||
log.Warningf("server: while saving leases: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
req, err := s.AcceptPacket()
|
||||
req, addr, err := srv.AcceptPacket()
|
||||
if err != nil {
|
||||
log.Errf("server: error reading packet: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lease := s.SelectLease(req, s.clock.Now())
|
||||
if err != nil {
|
||||
log.Err("server: couldn't find available lease")
|
||||
if req.MessageType != dhcp.MessageTypeBootRequest {
|
||||
log.Debugf("ignoring BOOTP message type %d", req.MessageType)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("available lease: %s", lease)
|
||||
continue
|
||||
switch req.DHCPType {
|
||||
case dhcp.DHCPMessageTypeDiscover:
|
||||
go srv.WriteDHCPResponse(req, addr)
|
||||
case dhcp.DHCPMessageTypeRequest:
|
||||
log.Debugf("not handling DHCP request")
|
||||
case dhcp.DHCPMessageTypeRelease:
|
||||
log.Debugf("not handling DHCP release")
|
||||
default:
|
||||
log.Debugf("not handling unknown request type %d", req.DHCPType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +143,7 @@ func New(cfg *config.Config) (*Server, error) {
|
||||
srv := &Server{
|
||||
Config: cfg,
|
||||
Pools: map[string]*iptools.Pool{},
|
||||
Static: map[string]*iptools.LeaseInfo{},
|
||||
Static: map[string]*leases.Info{},
|
||||
clock: clock.New(),
|
||||
}
|
||||
|
||||
@@ -121,18 +164,36 @@ func New(cfg *config.Config) (*Server, error) {
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (srv *Server) SelectLease(req *dhcp.BootRequest, t time.Time) *iptools.LeaseInfo {
|
||||
func (srv *Server) SelectLease(req *dhcp.Packet, t time.Time) *leases.Info {
|
||||
if li, ok := srv.Static[req.HostName]; ok {
|
||||
return li
|
||||
}
|
||||
|
||||
if pool, ok := srv.Pools[req.HostName]; ok {
|
||||
return pool.Peek(t, MaxResponseWait)
|
||||
return pool.Peek(req, t, MaxResponseWait)
|
||||
}
|
||||
|
||||
if pool, ok := srv.Pools[DefaultPool]; ok {
|
||||
return pool.Peek(t, MaxResponseWait)
|
||||
return pool.Peek(req, t, MaxResponseWait)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) AcceptLease(req *dhcp.Packet, li *leases.Info, t time.Time) error {
|
||||
if _, ok := srv.Static[li.HostName]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, pool := range srv.Pools {
|
||||
if _, ok := pool.Active[li.Addr]; ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := pool.Limbo[li.Addr]; ok {
|
||||
return pool.Accept(li.Addr, t)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("could not accept unknown lease: %s", li)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user