diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index c83200b..d2d64ba 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -46,6 +46,7 @@ import ( "github.com/rtr7/router7/internal/dhcp4d" "github.com/rtr7/router7/internal/multilisten" + "github.com/rtr7/router7/internal/netconfig" "github.com/rtr7/router7/internal/notify" "github.com/rtr7/router7/internal/oui" "github.com/rtr7/router7/internal/teelogger" @@ -58,8 +59,9 @@ var ( Help: "Number of non-expired DHCP leases", }) - iface = flag.String("interface", "lan0", "ethernet interface to listen for DHCPv4 requests on") - perm = flag.String("perm", "/perm", "path to replace /perm") + iface = flag.String("interface", "lan0", "ethernet interface to listen for DHCPv4 requests on") + perm = flag.String("perm", "/perm", "path to replace /perm") + domain = flag.String("domain", "lan", "domain name for your network") ) func updateNonExpired(leases []*dhcp4d.Lease) { @@ -259,7 +261,26 @@ func newSrv(permDir string) (*srv, error) { if err != nil { return nil, err } - handler, err := dhcp4d.NewHandler(permDir, ifc, *iface, nil) + + serverIP, err := netconfig.LinkAddress(permDir, *iface) + if err != nil { + return nil, err + } + var domainSearch []byte + domainSearch, err = dhcp4d.CompressNames("lan.", *domain) + if err != nil { + return nil, err + } + options := dhcp4.Options{ + dhcp4.OptionSubnetMask: []byte{255, 255, 255, 0}, + dhcp4.OptionRouter: []byte(serverIP), + dhcp4.OptionDomainNameServer: []byte(serverIP), + dhcp4.OptionTimeServer: []byte(serverIP), + dhcp4.OptionDomainName: []byte(*domain), + dhcp4.OptionDomainSearch: domainSearch, + } + + handler, err := dhcp4d.NewHandler(permDir, ifc, *iface, nil, options) if err != nil { return nil, err } diff --git a/init/init.go b/init/init.go index e2dba0b..70609ee 100644 --- a/init/init.go +++ b/init/init.go @@ -36,7 +36,7 @@ func main() { exec.Command(path.Join(cmdRoot, "backupd"), "-perm="+perm), exec.Command(path.Join(cmdRoot, "captured"), "-perm="+perm), exec.Command(path.Join(cmdRoot, "dhcp4"), "-perm="+perm), - exec.Command(path.Join(cmdRoot, "dhcp4d"), "-perm="+perm), + exec.Command(path.Join(cmdRoot, "dhcp4d"), fmt.Sprintf("-domain=%s", domain), "-perm="+perm), exec.Command(path.Join(cmdRoot, "dhcp6"), "-perm="+perm), exec.Command(path.Join(cmdRoot, "diagd"), "-perm="+perm), exec.Command(path.Join(cmdRoot, "dnsd"), fmt.Sprintf("-domain=%s", domain), "-perm="+perm), diff --git a/internal/dhcp4d/dhcp4d.go b/internal/dhcp4d/dhcp4d.go index 2bf2ea7..bfb3b21 100644 --- a/internal/dhcp4d/dhcp4d.go +++ b/internal/dhcp4d/dhcp4d.go @@ -18,6 +18,7 @@ package dhcp4d import ( "bytes" "encoding/hex" + "fmt" "log" "math/rand" "net" @@ -67,7 +68,7 @@ type Handler struct { leasesIP map[int]*Lease } -func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.PacketConn) (*Handler, error) { +func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.PacketConn, options dhcp4.Options) (*Handler, error) { serverIP, err := netconfig.LinkAddress(dir, ifaceName) if err != nil { return nil, err @@ -84,10 +85,24 @@ func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.Pac return nil, err } } + if options == nil { + var domainSearch []byte + domainSearch, err = CompressNames("lan.") + if err != nil { + return nil, err + } + options = dhcp4.Options{ + dhcp4.OptionSubnetMask: []byte{255, 255, 255, 0}, + dhcp4.OptionRouter: []byte(serverIP), + dhcp4.OptionDomainNameServer: []byte(serverIP), + dhcp4.OptionDomainName: []byte("lan"), + dhcp4.OptionDomainSearch: domainSearch, + } + } serverIP = serverIP.To4() start := make(net.IP, len(serverIP)) copy(start, serverIP) - start[len(start)-1] += 1 + start[len(start)-1]++ return &Handler{ rawConn: conn, iface: iface, @@ -97,14 +112,8 @@ func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.Pac start: start, leaseRange: 230, LeasePeriod: 20 * time.Minute, - options: dhcp4.Options{ - dhcp4.OptionSubnetMask: []byte{255, 255, 255, 0}, - dhcp4.OptionRouter: []byte(serverIP), - dhcp4.OptionDomainNameServer: []byte(serverIP), - dhcp4.OptionDomainName: []byte("lan"), - dhcp4.OptionDomainSearch: []byte{0x03, 'l', 'a', 'n', 0x00}, - }, - timeNow: time.Now, + options: options, + timeNow: time.Now, }, nil } @@ -389,3 +398,78 @@ func (h *Handler) expireLease(hwAddr string) bool { l.Expiry = time.Now() return true } + +func CompressNames(names ...string) ([]byte, error) { + b := make([]byte, 0, 255) + m := make(map[string]int) + var err error + for _, name := range names { + if name[len(name)-1] != '.' { + name += "." + } + b, err = pack([]byte(name), b, m) + if err != nil { + return []byte{}, err + } + } + return b, nil +} + +func pack(name []byte, msg []byte, compression map[string]int) ([]byte, error) { + oldMsg := msg + + // Add a trailing dot to canonicalize name. + if len(name) == 0 || name[len(name)-1] != '.' { + return oldMsg, fmt.Errorf("%s", "errNonCanonicalName") + } + + // Allow root domain. + if name[0] == '.' && len(name) == 1 { + return append(msg, 0), nil + } + + // Emit sequence of counted strings, chopping at dots. + for i, begin := 0, 0; i < len(name); i++ { + // Check for the end of the segment. + if name[i] == '.' { + // The two most significant bits have special meaning. + // It isn't allowed for segments to be long enough to + // need them. + if i-begin >= 1<<6 { + return oldMsg, fmt.Errorf("%s", "errSegTooLong") + } + + // Segments must have a non-zero length. + if i-begin == 0 { + return oldMsg, fmt.Errorf("%s", "errZeroSegLen") + } + + msg = append(msg, byte(i-begin)) + + for j := begin; j < i; j++ { + msg = append(msg, name[j]) + } + + begin = i + 1 + continue + } + + // We can only compress domain suffixes starting with a new + // segment. A pointer is two bytes with the two most significant + // bits set to 1 to indicate that it is a pointer. + if (i == 0 || name[i-1] == '.') && compression != nil { + if ptr, ok := compression[string(name[i:])]; ok { + // Hit. Emit a pointer instead of the rest of + // the domain. + return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil + } + + // Miss. Add the suffix to the compression table if the + // offset can be stored in the available 14 bytes. + if len(msg) <= int(^uint16(0)>>2) { + compression[string(name[i:])] = len(msg) + } + } + } + return append(msg, 0), nil +} diff --git a/internal/dhcp4d/dhcp4d_test.go b/internal/dhcp4d/dhcp4d_test.go index cd92078..59261cc 100644 --- a/internal/dhcp4d/dhcp4d_test.go +++ b/internal/dhcp4d/dhcp4d_test.go @@ -95,6 +95,7 @@ func testHandler(t *testing.T) (_ *Handler, cleanup func()) { }, "lan0", &noopSink{}, + nil, ) if err != nil { t.Fatal(err)