This commit is contained in:
Simon Zolin 2020-05-21 16:31:05 +03:00
parent 917f20fe1c
commit 8f9353782b
7 changed files with 164 additions and 324 deletions

View file

@ -127,7 +127,8 @@ func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease {
func (s *Server) dbStore() { func (s *Server) dbStore() {
var leases []leaseJSON var leases []leaseJSON
for _, l := range s.srv4.leases { leases4 := s.srv4.GetLeasesRef()
for _, l := range leases4 {
if l.Expiry.Unix() == 0 { if l.Expiry.Unix() == 0 {
continue continue
} }
@ -141,7 +142,8 @@ func (s *Server) dbStore() {
} }
if s.srv6 != nil { if s.srv6 != nil {
for _, l := range s.srv6.leases { leases6 := s.srv6.GetLeasesRef()
for _, l := range leases6 {
if l.Expiry.Unix() == 0 { if l.Expiry.Unix() == 0 {
continue continue
} }

View file

@ -1,11 +1,9 @@
package dhcpd package dhcpd
import ( import (
"fmt"
"net" "net"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings"
"time" "time"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
@ -58,18 +56,6 @@ const (
// Server - the current state of the DHCP server // Server - the current state of the DHCP server
type Server struct { 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 srv4 *V4Server
srv6 *V6Server srv6 *V6Server
@ -79,16 +65,6 @@ type Server struct {
onLeaseChanged onLeaseChangedT 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 // CheckConfig checks the configuration
func (s *Server) CheckConfig(config ServerConfig) error { func (s *Server) CheckConfig(config ServerConfig) error {
return nil return nil
@ -194,16 +170,15 @@ func (s *Server) Leases(flags int) []Lease {
return result 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 // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr { func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
if ip.To4() != nil { if ip.To4() != nil {
return s.srv4.FindMACbyIP4(ip) return s.srv4.FindMACbyIP(ip)
} }
return s.srv6.FindMACbyIP6(ip) return s.srv6.FindMACbyIP(ip)
}
// Reset internal state
func (s *Server) reset() {
s.srv4.Reset()
s.srv6.Reset()
} }

View file

@ -50,7 +50,7 @@ func TestDB(t *testing.T) {
_ = os.Remove("leases.db") _ = os.Remove("leases.db")
s.dbStore() s.dbStore()
s.reset() s.srv4.Reset()
s.dbLoad() s.dbLoad()

View file

@ -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()
}

View file

@ -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 <interface name>", 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
}

View file

@ -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) // GetLeases returns the list of current DHCP leases (thread-safe)
func (s *V4Server) GetLeases(flags int) []Lease { func (s *V4Server) GetLeases(flags int) []Lease {
var result []Lease var result []Lease
@ -95,8 +100,8 @@ func (s *V4Server) GetLeases(flags int) []Lease {
return result return result
} }
// FindMACbyIP4 - find a MAC address by IP address in the currently active DHCP leases // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
func (s *V4Server) FindMACbyIP4(ip net.IP) net.HardwareAddr { func (s *V4Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
now := time.Now().Unix() now := time.Now().Unix()
s.leasesLock.Lock() s.leasesLock.Lock()
@ -108,7 +113,7 @@ func (s *V4Server) FindMACbyIP4(ip net.IP) net.HardwareAddr {
} }
for _, l := range s.leases { for _, l := range s.leases {
if l.IP.Equal(ip4) { if net.IP.Equal(ip, ip4) {
unix := l.Expiry.Unix() unix := l.Expiry.Unix()
if unix > now || unix == leaseExpireStatic { if unix > now || unix == leaseExpireStatic {
return l.HWAddr return l.HWAddr
@ -154,7 +159,7 @@ func (s *V4Server) rmDynamicLease(lease Lease) error {
l = s.leases[i] l = s.leases[i]
} }
if bytes.Equal(l.IP, lease.IP) { if net.IP.Equal(l.IP, lease.IP) {
if l.Expiry.Unix() == leaseExpireStatic { if l.Expiry.Unix() == leaseExpireStatic {
return fmt.Errorf("static lease already exists") 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) 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 { func (s *V4Server) rmLease(lease Lease) error {
for i, l := range s.leases { 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) || if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
l.Hostname != lease.Hostname { l.Hostname != lease.Hostname {
@ -328,120 +333,146 @@ func (s *V4Server) reserveLease(mac net.HardwareAddr) *Lease {
return &l 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 // 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 { func (s *V4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int {
var lease *Lease 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])) resp.UpdateOption(dhcpv4.OptServerIdentifier(s.conf.dnsIPAddrs[0]))
switch req.MessageType() { switch req.MessageType() {
case dhcpv4.MessageTypeDiscover: case dhcpv4.MessageTypeDiscover:
lease = s.processDiscover(req, resp)
s.leasesLock.Lock()
defer s.leasesLock.Unlock()
lease = s.findLease(mac)
if lease == nil { 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 return 0
} }
if lease.Expiry.Unix() != leaseExpireStatic { case dhcpv4.MessageTypeRequest:
var toReply bool
lease.Expiry = time.Now().Add(s.conf.leaseTime) lease, toReply = s.processRequest(req, resp)
if lease == nil {
s.leasesLock.Lock() if toReply {
s.conf.notify(LeaseChangedDBStore) return 0
s.leasesLock.Unlock() }
return -1 // drop packet
s.conf.notify(LeaseChangedAdded)
} }
resp.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
} }
resp.YourIPAddr = make([]byte, 4) resp.YourIPAddr = make([]byte, 4)
@ -477,6 +508,11 @@ func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
return return
} }
if len(req.ClientHWAddr) != 6 {
log.Debug("DHCPv4: Invalid ClientHWAddr")
return
}
r := s.process(req, resp) r := s.process(req, resp)
if r < 0 { if r < 0 {
return return
@ -602,7 +638,7 @@ func v4Create(conf V4ServerConf) (*V4Server, error) {
if s.conf.ipEnd == nil { if s.conf.ipEnd == nil {
return nil, fmt.Errorf("DHCPv4: %s", err) 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] { s.conf.ipStart[3] > s.conf.ipEnd[3] {
return nil, fmt.Errorf("DHCPv4: range end IP should match range start IP") return nil, fmt.Errorf("DHCPv4: range end IP should match range start IP")
} }

View file

@ -75,8 +75,13 @@ func (s *V6Server) GetLeases(flags int) []Lease {
return result return result
} }
// FindMACbyIP6 - find a MAC address by IP address in the currently active DHCP leases // GetLeasesRef - get leases
func (s *V6Server) FindMACbyIP6(ip net.IP) net.HardwareAddr { 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() now := time.Now().Unix()
s.leasesLock.Lock() s.leasesLock.Lock()
@ -126,7 +131,7 @@ func (s *V6Server) rmDynamicLease(lease Lease) error {
l = s.leases[i] l = s.leases[i]
} }
if bytes.Equal(l.IP, lease.IP) { if net.IP.Equal(l.IP, lease.IP) {
if l.Expiry.Unix() == leaseExpireStatic { if l.Expiry.Unix() == leaseExpireStatic {
return fmt.Errorf("static lease already exists") 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) 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 { func (s *V6Server) rmLease(lease Lease) error {
for i, l := range s.leases { 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) || if !bytes.Equal(l.HWAddr, lease.HWAddr) ||
l.Hostname != lease.Hostname { l.Hostname != lease.Hostname {