diff --git a/cmd/conn_unix.go b/cmd/conn_unix.go new file mode 100644 index 0000000..3b156e2 --- /dev/null +++ b/cmd/conn_unix.go @@ -0,0 +1,70 @@ +// Copied with modifications from https://github.com/insomniacslk/dhcp/blob/master/dhcpv4/server4/conn_unix.go (under BSD-3-Clause license) +//go:build !windows + +package cmd + +import ( + "errors" + "fmt" + "net" + "os" + + "github.com/adrianokf/go-dhcp/pkg/types" + "golang.org/x/sys/unix" +) + +// NewIPv4UDPConn returns a UDP connection bound to both the interface and port +// given based on a IPv4 DGRAM socket. The UDP connection allows broadcasting. +// +// The interface must already be configured. +func NewIPv4UDPConn(iface string, addr *net.UDPAddr) (*net.UDPConn, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + if err != nil { + return nil, fmt.Errorf("cannot get a UDP socket: %v", err) + } + f := os.NewFile(uintptr(fd), "") + // net.FilePacketConn dups the FD, so we have to close this in any case. + defer f.Close() + + // Allow broadcasting. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_BROADCAST, 1); err != nil { + return nil, fmt.Errorf("cannot set broadcasting on socket: %v", err) + } + // Allow reusing the addr to aid debugging. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { + return nil, fmt.Errorf("cannot set reuseaddr on socket: %v", err) + } + // Allow reusing the port to aid debugging and testing. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, 1); err != nil { + return nil, fmt.Errorf("cannot set reuseport on socket: %v", err) + } + if len(iface) != 0 { + // Bind directly to the interface. + if err := unix.BindToDevice(fd, iface); err != nil { + return nil, fmt.Errorf("cannot bind to interface %s: %v", iface, err) + } + } + + if addr == nil { + addr = &net.UDPAddr{Port: types.ServerPort} + } + // Bind to the port. + saddr := unix.SockaddrInet4{Port: addr.Port} + if addr.IP != nil && addr.IP.To4() == nil { + return nil, fmt.Errorf("wrong address family (expected v4) for %s", addr.IP) + } + copy(saddr.Addr[:], addr.IP.To4()) + if err := unix.Bind(fd, &saddr); err != nil { + return nil, fmt.Errorf("cannot bind to port %d: %v", addr.Port, err) + } + + conn, err := net.FilePacketConn(f) + if err != nil { + return nil, err + } + udpconn, ok := conn.(*net.UDPConn) + if !ok { + return nil, errors.New("BUG: incorrect socket type, expected UDP") + } + return udpconn, nil +} diff --git a/cmd/root.go b/cmd/root.go index 3d08fbb..4161f06 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,20 +11,22 @@ import ( "github.com/adrianokf/go-dhcp/pkg/leases" "github.com/adrianokf/go-dhcp/pkg/messages" "github.com/adrianokf/go-dhcp/pkg/types" + "github.com/adrianokf/go-dhcp/pkg/util" "github.com/spf13/cobra" "go.uber.org/zap" ) var listenInterface string -var manager = leases.NewLeaseManager() +type Handler struct { + conn *net.UDPConn + leaseManager leases.LeaseManager +} var magic = [4]byte{0x63, 0x82, 0x53, 0x63} func parseOptions(data []byte) messages.Options { - s := zap.S() i := 0 - options := make(messages.Options) out: @@ -32,11 +34,11 @@ out: code := messages.OptionCode(data[i]) switch code { case messages.OptionEnd: - s.Debug("Found END option at offset ", i) + zap.S().Debug("Found END option at offset ", i) break out case messages.OptionPad: - s.Debug("Found padding option at offset ", i) + zap.S().Debug("Found padding option at offset ", i) i += 1 continue } @@ -44,12 +46,12 @@ out: size := int(data[i+1]) payload := data[i+2 : i+2+size] - s.Debugf("code=%d, size=%d, payload=%x", code, size, payload) + zap.S().Debugf("code=%d, size=%d, payload=%x", code, size, payload) options[code] = messages.Option{Code: code, Data: data[i+1 : i+2+size]} i += size + 2 } - s.Debugf("Parsed options: ", options) + zap.S().Debugf("Parsed options: ", options) return options } @@ -108,7 +110,18 @@ func prepareAck(request messages.DhcpMessage, lease leases.Lease) messages.DhcpM // sendMessage transmits a DHCP message with options via a UDP connection // The end option (code 255) is automatically appended and does not need to // be passed explicitly. -func sendMessage(conn *net.UDPConn, message messages.DhcpMessage, options []messages.Option) error { +func (h Handler) sendMessage(remote *net.UDPAddr, message messages.DhcpMessage, options []messages.Option) error { + // Send packets for 0.0.0.0 to broadcast address (255.255.255.255) instead + var destination *net.UDPAddr + if remote.IP.IsUnspecified() { + destination = &net.UDPAddr{ + IP: net.ParseIP("255.255.255.255"), + Port: types.ClientPort, + } + } else { + destination = remote + } + buf := make([]byte, 0) w := bytes.NewBuffer(buf) err := binary.Write(w, binary.BigEndian, message) @@ -127,16 +140,16 @@ func sendMessage(conn *net.UDPConn, message messages.DhcpMessage, options []mess } } - // Automatically add END option, so the caller doesn't - // need to specificy it for every invocation. + // Automatically add END option, so the caller doesn't need to specify it for every invocation. err = w.WriteByte(byte(messages.OptionEnd)) if err != nil { return err } msg := w.Bytes() - zap.S().Debug("Msg", msg) - _, err = conn.Write(msg) + zap.S().Debugf("Local addr: %s, remote addr: %s ", h.conn.LocalAddr(), destination) + zap.S().Debug("Msg data: ", msg) + _, err = h.conn.WriteToUDP(msg, destination) if err != nil { return err } @@ -144,21 +157,14 @@ func sendMessage(conn *net.UDPConn, message messages.DhcpMessage, options []mess return nil } -func handleOffer(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { +func (h Handler) sendOffer(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { s := zap.S() - lease, err := manager.Request(dhcp.Xid, dhcp.Chaddr) + lease, err := h.leaseManager.Request(dhcp.Xid, dhcp.Chaddr) if err != nil { panic(err) } offer := prepareOffer(dhcp, *lease) - localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") - clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") - conn, err := net.DialUDP("udp", localAddr, clientAddr) - if err != nil { - panic(err) - } - defer conn.Close() s.Info("Sending DHCPOFFER...") options := []messages.Option{ @@ -167,14 +173,14 @@ func handleOffer(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { Data: []byte{1, byte(messages.MessageTypeOffer)}, }, } - sendMessage(conn, offer, options) + h.sendMessage(remote, offer, options) return nil } -func handleAck(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { +func (h Handler) sendAck(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { s := zap.S() - lease, err := manager.Lookup(dhcp.Xid) + lease, err := h.leaseManager.Lookup(dhcp.Xid) if err != nil { panic(err) } @@ -188,31 +194,23 @@ func handleAck(dhcp messages.DhcpMessage, remote *net.UDPAddr) error { }, { Code: messages.OptionIPAddressLeaseTime, - Data: append([]byte{4}, u32tob(3600)...), + Data: append([]byte{4}, util.U32ToByte(3600)...), }, } s.Debug("Options: ", options) - localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") - clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") - conn, err := net.DialUDP("udp", localAddr, clientAddr) - if err != nil { - panic(err) - } - defer conn.Close() - s.Info("Sending DHCPACK") - sendMessage(conn, ack, options) + h.sendMessage(remote, ack, options) - lease, err = manager.Request(dhcp.Xid, dhcp.Chaddr) + lease, err = h.leaseManager.Request(dhcp.Xid, dhcp.Chaddr) if err != nil { return err } - s.Debug("Found lease", lease) + s.Debug("Found lease: ", lease) return nil } -func handleMsg(data []byte, remote *net.UDPAddr) { +func (h Handler) handleMsg(data []byte, remote *net.UDPAddr) { s := zap.S() s.Debugf("Connection from client %v", remote.IP) @@ -222,7 +220,7 @@ func handleMsg(data []byte, remote *net.UDPAddr) { binary.Read(reader, binary.BigEndian, &dhcp) dhcp.Debug(s) - if dhcp.Magic != [4]byte{0x63, 0x82, 0x53, 0x63} { + if dhcp.Magic != magic { panic("Invalid DHCP magic field") } @@ -236,10 +234,10 @@ func handleMsg(data []byte, remote *net.UDPAddr) { switch messages.MessageType(dhcpMsgType.Data[1]) { case messages.MessageTypeDiscover: - go handleOffer(dhcp, remote) + go h.sendOffer(dhcp, remote) case messages.MessageTypeRequest: - go handleAck(dhcp, remote) + go h.sendAck(dhcp, remote) } } @@ -249,7 +247,7 @@ func runServer(interfaceName string) { zap.L().Debug("Listening on all interfaces") addr, _ = net.ResolveUDPAddr("udp4", ":67") } else { - zap.S().Debugf("Listening on interface %s", interfaceName) + zap.S().Debug("Listening on interface ", interfaceName) iface, err := net.InterfaceByName(interfaceName) if err != nil { panic(err) @@ -275,17 +273,28 @@ func runServer(interfaceName string) { } } if ip == nil { - zap.S().Panicf("No IPv4 address associated with interface %s", interfaceName) + zap.S().Panic("No IPv4 address associated with interface ", interfaceName) } - fmt.Printf("%+v\n", ip) - addr, _ = net.ResolveUDPAddr("udp4", ip.String()+":67") + addr, _ = net.ResolveUDPAddr("udp4", ":67") + } + + var conn *net.UDPConn + var err error + if interfaceName != "all" { + conn, err = NewIPv4UDPConn(interfaceName, addr) + } else { + conn, err = net.ListenUDP("udp4", addr) } - conn, err := net.ListenUDP("udp4", addr) if err != nil { panic(err) } defer conn.Close() + handler := Handler{ + conn: conn, + leaseManager: *leases.NewLeaseManager(), + } + zap.S().Infof("Listening for incoming connections on %s", addr.String()) for { @@ -301,7 +310,7 @@ func runServer(interfaceName string) { zap.S().Warn("Not a valid remote IP address: ", remote) continue } - go handleMsg(buf[0:rlen], remoteAddr) + go handler.handleMsg(buf[0:rlen], remoteAddr) } } diff --git a/go.mod b/go.mod index 56ced9d..0faeedc 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,6 @@ require ( require ( github.com/spf13/cobra v1.8.0 - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sys v0.16.0 ) diff --git a/go.sum b/go.sum index 7e35cc0..16c8d0e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -9,8 +8,7 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -19,24 +17,19 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -54,6 +47,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -66,9 +61,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/leases/leases.go b/pkg/leases/leases.go index fe02d1e..604b922 100644 --- a/pkg/leases/leases.go +++ b/pkg/leases/leases.go @@ -1,8 +1,11 @@ package leases import ( + "encoding/json" "errors" "fmt" + "os" + "path/filepath" "time" "github.com/adrianokf/go-dhcp/pkg/types" @@ -16,12 +19,14 @@ const ( Requested ) +const DumpFile = "/var/lib/go-dhcp/leases.json" + type Lease struct { - TransactionId types.TxId - TTL time.Time - State LeaseState - ClientAddr types.HwAddr - Address types.Ipv4Addr + TransactionId types.TxId `json:"TransactionID"` + TTL time.Time `json:"TTL"` + State LeaseState `json:"State"` + ClientAddr types.HwAddr `json:"ClientAddr"` + Address types.Ipv4Addr `json:"Address"` } type ILeaseManager interface { @@ -35,9 +40,51 @@ type LeaseManager struct { count byte } +func (m LeaseManager) DumpLeases() { + err := os.MkdirAll(filepath.Dir(DumpFile), 0755) + if err != nil { + zap.S().Panic(err) + } + + f, err := os.Create(DumpFile) + if err != nil { + zap.S().Panic(err) + } + defer f.Close() + + leases := make([]Lease, len(m.leases)) + for _, l := range m.leases { + leases = append(leases, l) + } + + enc := json.NewEncoder(f) + enc.Encode(leases) + f.Sync() +} + +func LoadLeases() (map[types.TxId]Lease, error) { + var leaseMap = make(map[types.TxId]Lease) + + data, err := os.ReadFile(DumpFile) + if err != nil { + return leaseMap, err + } + var leases []Lease + json.Unmarshal(data, &leases) + + for _, l := range leases { + leaseMap[l.TransactionId] = l + } + return leaseMap, nil +} + func NewLeaseManager() *LeaseManager { + leases, err := LoadLeases() + if err != nil { + zap.S().Warnf("Could not restore saved leases from %s: %s", DumpFile, err) + } m := &LeaseManager{ - leases: make(map[types.TxId]Lease), + leases: leases, } return m } @@ -62,6 +109,9 @@ func (m *LeaseManager) Request(xid types.TxId, clientAddr types.HwAddr) (*Lease, m.count += 1 zap.S().Debug("lease=", lease) + + m.DumpLeases() + return &lease, nil } @@ -71,6 +121,9 @@ func (m LeaseManager) Release(l Lease) error { return fmt.Errorf("invalid lease %v", l) } delete(m.leases, l.TransactionId) + + m.DumpLeases() + return nil } diff --git a/pkg/types/json.go b/pkg/types/json.go new file mode 100644 index 0000000..353ce21 --- /dev/null +++ b/pkg/types/json.go @@ -0,0 +1,46 @@ +package types + +import ( + "fmt" + "net" + "strconv" + + util "github.com/adrianokf/go-dhcp/pkg/util" +) + +func (txid *TxId) UnmarshalText(text []byte) (err error) { + result, err := strconv.ParseUint(string(text), 16, 32) + if err != nil { + return err + } + + copy(txid[:], util.U32ToByte(uint32(result))) + return nil +} + +func (id TxId) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("%x", id)), nil +} + +func (addr *Ipv4Addr) UnmarshalText(text []byte) (err error) { + ip := net.ParseIP(string(text)) + fmt.Println(ip) + if ip == nil { + return fmt.Errorf("could not parse IP address: '%s'", text) + } + *addr = Ipv4AddrFromNetIP(ip) + return nil +} + +func (addr Ipv4Addr) MarshalText() ([]byte, error) { + return addr.ToNetIPv4().MarshalText() +} + +func (addr HwAddr) MarshalText() ([]byte, error) { + return []byte(addr.String()), nil +} + +func (addr *HwAddr) UnmarshalText(text []byte) (err error) { + *addr, err = HwAddrFromString(string(text)) + return err +} diff --git a/pkg/types/types.go b/pkg/types/types.go index 448da90..e72ccdd 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,5 +1,37 @@ package types +import ( + "fmt" + "net" +) + type TxId [4]byte type Ipv4Addr [4]byte type HwAddr [16]byte + +const ( + ServerPort = 67 + ClientPort = 68 +) + +func Ipv4AddrFromNetIP(ip net.IP) Ipv4Addr { + ipv4 := ip.To4() + return Ipv4Addr{ipv4[0], ipv4[1], ipv4[2], ipv4[3]} +} + +func (addr Ipv4Addr) ToNetIPv4() net.IP { + return net.IPv4(addr[0], addr[1], addr[2], addr[3]) +} + +func (addr Ipv4Addr) String() string { + return addr.ToNetIPv4().String() +} + +func (addr HwAddr) String() string { + return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]) +} + +func HwAddrFromString(s string) (addr HwAddr, err error) { + _, err = fmt.Sscanf(s, "%02x:%02x:%02x:%02x:%02x:%02x", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5]) + return addr, err +} diff --git a/cmd/util.go b/pkg/util/util.go similarity index 69% rename from cmd/util.go rename to pkg/util/util.go index 88b731e..371d39d 100644 --- a/cmd/util.go +++ b/pkg/util/util.go @@ -1,10 +1,10 @@ -package cmd +package util import ( "encoding/binary" ) -func u32tob(u uint32) []byte { +func U32ToByte(u uint32) []byte { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, u) return buf