diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 27233965..57e0c513 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -10,6 +10,7 @@ import ( "github.com/AdguardTeam/golibs/log" "github.com/krolaw/dhcp4" + "github.com/sparrc/go-ping" ) const defaultDiscoverTime = time.Second * 3 @@ -33,6 +34,10 @@ type ServerConfig struct { RangeStart string `json:"range_start" yaml:"range_start"` RangeEnd string `json:"range_end" yaml:"range_end"` LeaseDuration uint `json:"lease_duration" yaml:"lease_duration"` // in seconds + + // IP conflict detector: time (ms) to wait for ICMP reply. + // 0: disable + ICMPTimeout uint `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` } // Server - the current state of the DHCP server @@ -325,6 +330,51 @@ func (s *Server) ServeDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options dh return nil } +// Send ICMP to the specified machine +// Return TRUE if it doesn't reply, which probably means that the IP is available +func (s *Server) addrAvailable(target net.IP) bool { + + if s.ICMPTimeout == 0 { + return true + } + + pinger, err := ping.NewPinger(target.String()) + if err != nil { + log.Error("ping.NewPinger(): %v", err) + return true + } + + pinger.SetPrivileged(true) + pinger.Timeout = time.Duration(s.ICMPTimeout) * time.Millisecond + pinger.Count = 1 + reply := false + pinger.OnRecv = func(pkt *ping.Packet) { + // log.Tracef("Received ICMP Reply from %v", target) + reply = true + } + log.Tracef("Sending ICMP Echo to %v", target) + pinger.Run() + + if reply { + log.Info("DHCP: IP conflict: %v is already used by another device", target) + return false + } + + log.Tracef("ICMP procedure is complete: %v", target) + return true +} + +// Add the specified IP to the black list for a time period +func (s *Server) blacklistLease(lease *Lease) { + hw := make(net.HardwareAddr, 6) + s.reserveIP(lease.IP, hw) + s.Lock() + lease.HWAddr = hw + lease.Hostname = "" + lease.Expiry = time.Now().Add(s.leaseTime) + s.Unlock() +} + // Return TRUE if DHCP packet is correct func isValidPacket(p dhcp4.Packet) bool { hw := p.CHAddr() @@ -358,6 +408,12 @@ func (s *Server) handleDiscover(p dhcp4.Packet, options dhcp4.Options) dhcp4.Pac return nil } + if !s.addrAvailable(lease.IP) { + s.blacklistLease(lease) + lease = nil + continue + } + break } diff --git a/go.mod b/go.mod index 2f0dd5bd..62035c9c 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/miekg/dns v1.1.1 github.com/shirou/gopsutil v2.18.10+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect + github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0 github.com/stretchr/testify v1.2.2 go.uber.org/goleak v0.10.0 golang.org/x/net v0.0.0-20190119204137-ed066c81e75e diff --git a/go.sum b/go.sum index 51c15f76..c4bced69 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/shirou/gopsutil v2.18.10+incompatible h1:cy84jW6EVRPa5g9HAHrlbxMSIjBh github.com/shirou/gopsutil v2.18.10+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0 h1:mu7brOsdaH5Dqf93vdch+mr/0To8Sgc+yInt/jE/RJM= +github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=