diff --git a/dhcpd/db.go b/dhcpd/db.go index 111e427a..b01f9635 100644 --- a/dhcpd/db.go +++ b/dhcpd/db.go @@ -127,7 +127,8 @@ func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease { func (s *Server) dbStore() { var leases []leaseJSON - for _, l := range s.srv4.leases { + leases4 := s.srv4.GetLeasesRef() + for _, l := range leases4 { if l.Expiry.Unix() == 0 { continue } @@ -141,7 +142,8 @@ func (s *Server) dbStore() { } if s.srv6 != nil { - for _, l := range s.srv6.leases { + leases6 := s.srv6.GetLeasesRef() + for _, l := range leases6 { if l.Expiry.Unix() == 0 { continue } diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 7c5ac943..8d7d5244 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -1,11 +1,9 @@ package dhcpd import ( - "fmt" "net" "net/http" "path/filepath" - "strings" "time" "github.com/AdguardTeam/golibs/log" @@ -58,18 +56,6 @@ const ( // Server - the current state of the DHCP server type Server struct { - // conn *filterConn // listening UDP socket - - // ipnet *net.IPNet // if interface name changes, this needs to be reset - - // cond *sync.Cond // Synchronize worker thread with main thread - // mutex sync.Mutex // Mutex for 'cond' - // running bool // Set if the worker thread is running - // stopping bool // Set if the worker thread should be stopped - - // leases - // leaseOptions dhcp4.Options // parsed from config GatewayIP and SubnetMask - srv4 *V4Server srv6 *V6Server @@ -79,16 +65,6 @@ type Server struct { onLeaseChanged onLeaseChangedT } -// Print information about the available network interfaces -func printInterfaces() { - ifaces, _ := net.Interfaces() - var buf strings.Builder - for i := range ifaces { - buf.WriteString(fmt.Sprintf("\"%s\", ", ifaces[i].Name)) - } - log.Info("Available network interfaces: %s", buf.String()) -} - // CheckConfig checks the configuration func (s *Server) CheckConfig(config ServerConfig) error { return nil @@ -194,16 +170,15 @@ func (s *Server) Leases(flags int) []Lease { return result } +// AddStaticLease adds a static lease (thread-safe) +func (s *Server) AddStaticLease(lease Lease) error { + return s.srv4.AddStaticLease(lease) +} + // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr { if ip.To4() != nil { - return s.srv4.FindMACbyIP4(ip) + return s.srv4.FindMACbyIP(ip) } - return s.srv6.FindMACbyIP6(ip) -} - -// Reset internal state -func (s *Server) reset() { - s.srv4.Reset() - s.srv6.Reset() + return s.srv6.FindMACbyIP(ip) } diff --git a/dhcpd/dhcpd_test.go b/dhcpd/dhcpd_test.go index ca472c11..2caae1fb 100644 --- a/dhcpd/dhcpd_test.go +++ b/dhcpd/dhcpd_test.go @@ -50,7 +50,7 @@ func TestDB(t *testing.T) { _ = os.Remove("leases.db") s.dbStore() - s.reset() + s.srv4.Reset() s.dbLoad() diff --git a/dhcpd/filter_conn.go b/dhcpd/filter_conn.go deleted file mode 100644 index 70cfa939..00000000 --- a/dhcpd/filter_conn.go +++ /dev/null @@ -1,63 +0,0 @@ -package dhcpd - -import ( - "net" - - "github.com/joomcode/errorx" - "golang.org/x/net/ipv4" -) - -// filterConn listens to 0.0.0.0:67, but accepts packets only from specific interface -// This is necessary for DHCP daemon to work, since binding to IP address doesn't -// us access to see Discover/Request packets from clients. -// -// TODO: on windows, controlmessage does not work, try to find out another way -// https://github.com/golang/net/blob/master/ipv4/payload.go#L13 -type filterConn struct { - iface net.Interface - conn *ipv4.PacketConn -} - -func newFilterConn(iface net.Interface, address string) (*filterConn, error) { - c, err := net.ListenPacket("udp4", address) - if err != nil { - return nil, errorx.Decorate(err, "Couldn't listen to %s on UDP4", address) - } - - p := ipv4.NewPacketConn(c) - err = p.SetControlMessage(ipv4.FlagInterface, true) - if err != nil { - c.Close() - return nil, errorx.Decorate(err, "Couldn't set control message FlagInterface on connection") - } - - return &filterConn{iface: iface, conn: p}, nil -} - -func (f *filterConn) ReadFrom(b []byte) (int, net.Addr, error) { - for { // read until we find a suitable packet - n, cm, addr, err := f.conn.ReadFrom(b) - if err != nil { - return 0, addr, errorx.Decorate(err, "Error when reading from socket") - } - if cm == nil { - // no controlmessage was passed, so pass the packet to the caller - return n, addr, nil - } - if cm.IfIndex == f.iface.Index { - return n, addr, nil - } - // packet doesn't match criteria, drop it - } -} - -func (f *filterConn) WriteTo(b []byte, addr net.Addr) (int, error) { - cm := ipv4.ControlMessage{ - IfIndex: f.iface.Index, - } - return f.conn.WriteTo(b, &cm, addr) -} - -func (f *filterConn) Close() error { - return f.conn.Close() -} diff --git a/dhcpd/standalone/main.go b/dhcpd/standalone/main.go deleted file mode 100644 index a9aab9ee..00000000 --- a/dhcpd/standalone/main.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "net" - "os" - "os/signal" - "syscall" - "time" - - "github.com/AdguardTeam/AdGuardHome/dhcpd" - "github.com/AdguardTeam/golibs/log" - "github.com/krolaw/dhcp4" -) - -func main() { - if len(os.Args) < 2 { - log.Printf("Usage: %s ", os.Args[0]) - os.Exit(64) - } - - ifaceName := os.Args[1] - present, err := dhcpd.CheckIfOtherDHCPServersPresent(ifaceName) - if err != nil { - panic(err) - } - log.Printf("Found DHCP server? %v", present) - if present { - log.Printf("Will not start DHCP server because there's already running one on the network") - os.Exit(1) - } - - iface, err := net.InterfaceByName(ifaceName) - if err != nil { - panic(err) - } - - // get ipv4 address of an interface - ifaceIPNet := getIfaceIPv4(iface) - if ifaceIPNet == nil { - panic(err) - } - - // append 10 to server's IP address as start - start := dhcp4.IPAdd(ifaceIPNet.IP, 10) - // lease range is 100 IP's, but TODO: don't go beyond end of subnet mask - stop := dhcp4.IPAdd(start, 100) - - server := dhcpd.Server{} - config := dhcpd.ServerConfig{ - InterfaceName: ifaceName, - RangeStart: start.String(), - RangeEnd: stop.String(), - SubnetMask: "255.255.255.0", - GatewayIP: "192.168.7.1", - } - log.Printf("Starting DHCP server") - err = server.Init(config) - if err != nil { - panic(err) - } - err = server.Start() - if err != nil { - panic(err) - } - - time.Sleep(time.Second) - log.Printf("Stopping DHCP server") - err = server.Stop() - if err != nil { - panic(err) - } - log.Printf("Starting DHCP server") - err = server.Start() - if err != nil { - panic(err) - } - log.Printf("Starting DHCP server while it's already running") - err = server.Start() - if err != nil { - panic(err) - } - log.Printf("Now serving DHCP") - signalChannel := make(chan os.Signal, 1) - signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM) - <-signalChannel - -} - -// return first IPv4 address of an interface, if there is any -func getIfaceIPv4(iface *net.Interface) *net.IPNet { - ifaceAddrs, err := iface.Addrs() - if err != nil { - panic(err) - } - - for _, addr := range ifaceAddrs { - ipnet, ok := addr.(*net.IPNet) - if !ok { - // not an IPNet, should not happen - log.Fatalf("SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet", addr) - } - - if ipnet.IP.To4() == nil { - log.Printf("Got IP that is not IPv4: %v", ipnet.IP) - continue - } - - log.Printf("Got IP that is IPv4: %v", ipnet.IP) - return &net.IPNet{ - IP: ipnet.IP.To4(), - Mask: ipnet.Mask, - } - } - return nil -} diff --git a/dhcpd/v4.go b/dhcpd/v4.go index 04928c7c..a15600f8 100644 --- a/dhcpd/v4.go +++ b/dhcpd/v4.go @@ -78,6 +78,11 @@ func (s *V4Server) ResetLeases(leases []*Lease) { } } +// GetLeasesRef - get leases +func (s *V4Server) GetLeasesRef() []*Lease { + return s.leases +} + // GetLeases returns the list of current DHCP leases (thread-safe) func (s *V4Server) GetLeases(flags int) []Lease { var result []Lease @@ -95,8 +100,8 @@ func (s *V4Server) GetLeases(flags int) []Lease { return result } -// FindMACbyIP4 - find a MAC address by IP address in the currently active DHCP leases -func (s *V4Server) FindMACbyIP4(ip net.IP) net.HardwareAddr { +// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases +func (s *V4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { now := time.Now().Unix() s.leasesLock.Lock() @@ -108,7 +113,7 @@ func (s *V4Server) FindMACbyIP4(ip net.IP) net.HardwareAddr { } for _, l := range s.leases { - if l.IP.Equal(ip4) { + if net.IP.Equal(ip, ip4) { unix := l.Expiry.Unix() if unix > now || unix == leaseExpireStatic { return l.HWAddr @@ -154,7 +159,7 @@ func (s *V4Server) rmDynamicLease(lease Lease) error { l = s.leases[i] } - if bytes.Equal(l.IP, lease.IP) { + if net.IP.Equal(l.IP, lease.IP) { if l.Expiry.Unix() == leaseExpireStatic { return fmt.Errorf("static lease already exists") @@ -173,10 +178,10 @@ func (s *V4Server) addLease(l *Lease) { log.Debug("DHCPv4: added lease %s <-> %s", l.IP, l.HWAddr) } -// Remove a lease with the same properies +// Remove a lease with the same properties func (s *V4Server) rmLease(lease Lease) error { for i, l := range s.leases { - if bytes.Equal(l.IP, lease.IP) { + if net.IP.Equal(l.IP, lease.IP) { if !bytes.Equal(l.HWAddr, lease.HWAddr) || l.Hostname != lease.Hostname { @@ -328,120 +333,146 @@ func (s *V4Server) reserveLease(mac net.HardwareAddr) *Lease { return &l } +// Process Discover request and return lease +func (s *V4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lease { + mac := req.ClientHWAddr + + s.leasesLock.Lock() + defer s.leasesLock.Unlock() + + lease := s.findLease(mac) + if lease == nil { + toStore := false + for lease == nil { + lease = s.reserveLease(mac) + if lease == nil { + log.Debug("DHCPv4: No more IP addresses") + if toStore { + s.conf.notify(LeaseChangedDBStore) + } + return nil + } + + toStore = true + + if !s.addrAvailable(lease.IP) { + s.blacklistLease(lease) + lease = nil + continue + } + break + } + + s.conf.notify(LeaseChangedDBStore) + + // s.conf.notify(LeaseChangedBlacklisted) + + } else { + reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress) + if len(reqIP) != 0 && + !bytes.Equal(reqIP, lease.IP) { + log.Debug("DHCPv4: different RequestedIP: %v != %v", reqIP, lease.IP) + } + } + + hostname := req.Options.Get(dhcpv4.OptionHostName) + lease.Hostname = string(hostname) + + resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer)) + return lease +} + +// Process Request request and return lease +// Return false if we don't need to reply +func (s *V4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lease, bool) { + var lease *Lease + mac := req.ClientHWAddr + hostname := req.Options.Get(dhcpv4.OptionHostName) + reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress) + + sid := req.Options.Get(dhcpv4.OptionServerIdentifier) + if len(sid) == 0 { + log.Debug("DHCPv4: No OptionServerIdentifier in Request message for %s", mac) + return nil, false + } + if !bytes.Equal(sid, s.conf.dnsIPAddrs[0]) { + log.Debug("DHCPv4: Bad OptionServerIdentifier in Request message for %s", mac) + return nil, false + } + + if len(reqIP) != 4 { + log.Debug("DHCPv4: Bad OptionRequestedIPAddress in Request message for %s", mac) + return nil, false + } + + s.leasesLock.Lock() + for _, l := range s.leases { + if bytes.Equal(l.HWAddr, mac) { + if !bytes.Equal(l.IP, reqIP) { + s.leasesLock.Unlock() + log.Debug("DHCPv4: Mismatched OptionRequestedIPAddress in Request message for %s", mac) + return nil, true + } + + if !bytes.Equal([]byte(l.Hostname), hostname) { + s.leasesLock.Unlock() + log.Debug("DHCPv4: Mismatched OptionHostName in Request message for %s", mac) + return nil, true + } + + lease = l + break + } + } + s.leasesLock.Unlock() + + if lease == nil { + log.Debug("DHCPv4: No lease for %s", mac) + return nil, true + } + + if lease.Expiry.Unix() != leaseExpireStatic { + + lease.Expiry = time.Now().Add(s.conf.leaseTime) + + s.leasesLock.Lock() + s.conf.notify(LeaseChangedDBStore) + s.leasesLock.Unlock() + + s.conf.notify(LeaseChangedAdded) + } + + resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) + return lease, true +} + // Find a lease associated with MAC and prepare response +// Return 1: OK +// Return 0: error; reply with Nak +// Return -1: error; don't reply func (s *V4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int { var lease *Lease - mac := req.ClientHWAddr - if len(mac) != 6 { - log.Debug("DHCPv4: Invalid ClientHWAddr") - return -1 - } - hostname := req.Options.Get(dhcpv4.OptionHostName) - reqIP := req.Options.Get(dhcpv4.OptionRequestedIPAddress) resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0])) switch req.MessageType() { case dhcpv4.MessageTypeDiscover: - - s.leasesLock.Lock() - defer s.leasesLock.Unlock() - - lease = s.findLease(mac) + lease = s.processDiscover(req, resp) if lease == nil { - toStore := false - for lease == nil { - lease = s.reserveLease(mac) - if lease == nil { - log.Debug("DHCPv4: No more IP addresses") - if toStore { - s.conf.notify(LeaseChangedDBStore) - } - return 0 - } - - toStore = true - - if !s.addrAvailable(lease.IP) { - s.blacklistLease(lease) - lease = nil - continue - } - break - } - - s.conf.notify(LeaseChangedDBStore) - - // s.conf.notify(LeaseChangedBlacklisted) - - } else { - if len(reqIP) != 0 && - !bytes.Equal(reqIP, lease.IP) { - log.Debug("DHCPv4: different RequestedIP: %v != %v", reqIP, lease.IP) - } - } - - lease.Hostname = string(hostname) - - resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer)) - - case dhcpv4.MessageTypeRequest: - - sid := req.Options.Get(dhcpv4.OptionServerIdentifier) - if len(sid) == 0 { - log.Debug("DHCPv4: No OptionServerIdentifier in Request message for %s", mac) - return -1 - } - if !bytes.Equal(sid, s.conf.dnsIPAddrs[0]) { - log.Debug("DHCPv4: Bad OptionServerIdentifier in Request message for %s", mac) - return -1 - } - - if len(reqIP) != 4 { - log.Debug("DHCPv4: Bad OptionRequestedIPAddress in Request message for %s", mac) - return -1 - } - - s.leasesLock.Lock() - for _, l := range s.leases { - if bytes.Equal(l.HWAddr, mac) { - if !bytes.Equal(l.IP, reqIP) { - s.leasesLock.Unlock() - log.Debug("DHCPv4: Mismatched OptionRequestedIPAddress in Request message for %s", mac) - return -1 - } - - if !bytes.Equal([]byte(l.Hostname), hostname) { - s.leasesLock.Unlock() - log.Debug("DHCPv4: Mismatched OptionHostName in Request message for %s", mac) - return -1 - } - - lease = l - break - } - } - s.leasesLock.Unlock() - - if lease == nil { - log.Debug("DHCPv4: No lease for %s", mac) return 0 } - if lease.Expiry.Unix() != leaseExpireStatic { - - lease.Expiry = time.Now().Add(s.conf.leaseTime) - - s.leasesLock.Lock() - s.conf.notify(LeaseChangedDBStore) - s.leasesLock.Unlock() - - s.conf.notify(LeaseChangedAdded) + case dhcpv4.MessageTypeRequest: + var toReply bool + lease, toReply = s.processRequest(req, resp) + if lease == nil { + if toReply { + return 0 + } + return -1 // drop packet } - - resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck)) } resp.YourIPAddr = make([]byte, 4) @@ -477,6 +508,11 @@ func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4 return } + if len(req.ClientHWAddr) != 6 { + log.Debug("DHCPv4: Invalid ClientHWAddr") + return + } + r := s.process(req, resp) if r < 0 { return @@ -602,7 +638,7 @@ func v4Create(conf V4ServerConf) (*V4Server, error) { if s.conf.ipEnd == nil { return nil, fmt.Errorf("DHCPv4: %s", err) } - if !bytes.Equal(s.conf.ipStart[:3], s.conf.ipEnd[:3]) || + if !net.IP.Equal(s.conf.ipStart[:3], s.conf.ipEnd[:3]) || s.conf.ipStart[3] > s.conf.ipEnd[3] { return nil, fmt.Errorf("DHCPv4: range end IP should match range start IP") } diff --git a/dhcpd/v6.go b/dhcpd/v6.go index a4a2d70f..33a570f1 100644 --- a/dhcpd/v6.go +++ b/dhcpd/v6.go @@ -75,8 +75,13 @@ func (s *V6Server) GetLeases(flags int) []Lease { return result } -// FindMACbyIP6 - find a MAC address by IP address in the currently active DHCP leases -func (s *V6Server) FindMACbyIP6(ip net.IP) net.HardwareAddr { +// GetLeasesRef - get leases +func (s *V6Server) GetLeasesRef() []*Lease { + return s.leases +} + +// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases +func (s *V6Server) FindMACbyIP(ip net.IP) net.HardwareAddr { now := time.Now().Unix() s.leasesLock.Lock() @@ -126,7 +131,7 @@ func (s *V6Server) rmDynamicLease(lease Lease) error { l = s.leases[i] } - if bytes.Equal(l.IP, lease.IP) { + if net.IP.Equal(l.IP, lease.IP) { if l.Expiry.Unix() == leaseExpireStatic { return fmt.Errorf("static lease already exists") @@ -191,10 +196,10 @@ func (s *V6Server) addLease(l *Lease) { log.Debug("DHCPv6: added lease %s <-> %s", l.IP, l.HWAddr) } -// Remove a lease with the same properies +// Remove a lease with the same properties func (s *V6Server) rmLease(lease Lease) error { for i, l := range s.leases { - if bytes.Equal(l.IP, lease.IP) { + if net.IP.Equal(l.IP, lease.IP) { if !bytes.Equal(l.HWAddr, lease.HWAddr) || l.Hostname != lease.Hostname {