diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 08624100..01e11959 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -395,7 +395,7 @@ Response: 200 OK { - "config":{ + "v4":{ "enabled":false, "interface_name":"...", "gateway_ip":"...", @@ -405,7 +405,7 @@ Response: "lease_duration":60, "icmp_timeout_msec":0 }, - "config_v6":{ + "v6":{ "enabled":false, "range_start":"...", "lease_duration":60, @@ -468,16 +468,17 @@ Request: POST /control/dhcp/set_config { - "enabled":true, - "interface_name":"vboxnet0", - - "gateway_ip":"192.169.56.1", - "subnet_mask":"255.255.255.0", - "range_start":"192.169.56.100", - "range_end":"192.169.56.200", - "lease_duration":60, - "icmp_timeout_msec":0, + "v4":{ + "enabled":true, + "interface_name":"vboxnet0", + "gateway_ip":"192.169.56.1", + "subnet_mask":"255.255.255.0", + "range_start":"192.169.56.100", + "range_end":"192.169.56.200", + "lease_duration":60, + "icmp_timeout_msec":0, + }, "v6":{ "enabled":false, "range_start":"...", diff --git a/dhcpd/dhcp_http.go b/dhcpd/dhcp_http.go index 1c509fe6..65c58cd8 100644 --- a/dhcpd/dhcp_http.go +++ b/dhcpd/dhcp_http.go @@ -40,6 +40,43 @@ func convertLeases(inputLeases []Lease, includeExpires bool) []map[string]string return leases } +type v4ServerConfJSON struct { + Enabled bool `json:"enabled"` + InterfaceName string `json:"interface_name"` + GatewayIP string `json:"gateway_ip"` + SubnetMask string `json:"subnet_mask"` + RangeStart string `json:"range_start"` + RangeEnd string `json:"range_end"` + LeaseDuration uint32 `json:"lease_duration"` + ICMPTimeout uint32 `json:"icmp_timeout_msec"` +} + +func v4ServerConfToJSON(c V4ServerConf) v4ServerConfJSON { + return v4ServerConfJSON{ + Enabled: c.Enabled, + InterfaceName: c.InterfaceName, + GatewayIP: c.GatewayIP, + SubnetMask: c.SubnetMask, + RangeStart: c.RangeStart, + RangeEnd: c.RangeEnd, + LeaseDuration: c.LeaseDuration, + ICMPTimeout: c.ICMPTimeout, + } +} + +func v4JSONToServerConf(c v4ServerConfJSON) V4ServerConf { + return V4ServerConf{ + Enabled: c.Enabled, + InterfaceName: c.InterfaceName, + GatewayIP: c.GatewayIP, + SubnetMask: c.SubnetMask, + RangeStart: c.RangeStart, + RangeEnd: c.RangeEnd, + LeaseDuration: c.LeaseDuration, + ICMPTimeout: c.ICMPTimeout, + } +} + type v6ServerConfJSON struct { Enabled bool `json:"enabled"` RangeStart string `json:"range_start"` @@ -66,8 +103,8 @@ func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { leases := convertLeases(s.Leases(LeasesDynamic), true) staticLeases := convertLeases(s.Leases(LeasesStatic), false) status := map[string]interface{}{ - "config": s.conf.Conf4, - "config_v6": v6ServerConfToJSON(s.conf.Conf6), + "v4": v4ServerConfToJSON(s.conf.Conf4), + "v6": v6ServerConfToJSON(s.conf.Conf6), "leases": leases, "static_leases": staticLeases, } @@ -87,8 +124,8 @@ type staticLeaseJSON struct { } type dhcpServerConfigJSON struct { - V4ServerConf `json:",inline"` - V6 v6ServerConfJSON `json:"v6"` + V4 v4ServerConfJSON `json:"v4"` + V6 v6ServerConfJSON `json:"v6"` } func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { @@ -110,7 +147,7 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { log.Error("failed to stop the DHCP server: %s", err) } - v4conf := newconfig.V4ServerConf + v4conf := v4JSONToServerConf(newconfig.V4) v4conf.notify = s.conf.Conf4.notify s4, err := v4Create(v4conf) if err != nil { @@ -131,10 +168,10 @@ func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { s.conf.ConfigModified() - if newconfig.Enabled { - staticIP, err := HasStaticIP(newconfig.InterfaceName) + if newconfig.V4.Enabled { + staticIP, err := HasStaticIP(newconfig.V4.InterfaceName) if !staticIP && err == nil { - err = SetStaticIP(newconfig.InterfaceName) + err = SetStaticIP(newconfig.V4.InterfaceName) if err != nil { httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) return diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 8d7d5244..38b3b3d3 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -56,8 +56,8 @@ const ( // Server - the current state of the DHCP server type Server struct { - srv4 *V4Server - srv6 *V6Server + srv4 DHCPServer + srv6 DHCPServer conf ServerConfig @@ -75,6 +75,8 @@ func Create(config ServerConfig) *Server { s := Server{} config.Conf4.notify = s.onNotify config.Conf6.notify = s.onNotify + s.conf.HTTPRegister = config.HTTPRegister + s.conf.ConfigModified = config.ConfigModified s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename) if !webHandlersRegistered && s.conf.HTTPRegister != nil { @@ -84,13 +86,13 @@ func Create(config ServerConfig) *Server { var err error s.srv4, err = v4Create(config.Conf4) - if s.srv4 == nil { + if err != nil { log.Error("%s", err) return nil } s.srv6, err = v6Create(config.Conf6) - if s.srv6 == nil { + if err != nil { log.Error("%s", err) return nil } @@ -125,8 +127,8 @@ func (s *Server) notify(flags int) { // WriteDiskConfig - write configuration func (s *Server) WriteDiskConfig(c *ServerConfig) { - s.srv4.WriteDiskConfig(&c.Conf4) - s.srv6.WriteDiskConfig(&c.Conf6) + s.srv4.WriteDiskConfig4(&c.Conf4) + s.srv6.WriteDiskConfig6(&c.Conf6) } // Start will listen on port 67 and serve DHCP requests. @@ -170,11 +172,6 @@ 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 { diff --git a/dhcpd/helpers.go b/dhcpd/helpers.go index b6ef89c6..e816e183 100644 --- a/dhcpd/helpers.go +++ b/dhcpd/helpers.go @@ -39,6 +39,26 @@ func parseIPv4(text string) (net.IP, error) { return result.To4(), nil } +// Get IPv4 address list +func getIfaceIPv4(iface net.Interface) []net.IP { + addrs, err := iface.Addrs() + if err != nil { + return nil + } + + var res []net.IP + for _, a := range addrs { + ipnet, ok := a.(*net.IPNet) + if !ok { + continue + } + if ipnet.IP.To4() != nil { + res = append(res, ipnet.IP.To4()) + } + } + return res +} + // Return TRUE if subnet mask is correct (e.g. 255.255.255.0) func isValidSubnetMask(mask net.IP) bool { var n uint32 diff --git a/dhcpd/server.go b/dhcpd/server.go new file mode 100644 index 00000000..a8e15101 --- /dev/null +++ b/dhcpd/server.go @@ -0,0 +1,61 @@ +package dhcpd + +import ( + "net" + "time" +) + +// DHCPServer - DHCP server interface +type DHCPServer interface { + ResetLeases(leases []*Lease) + GetLeases(flags int) []Lease + GetLeasesRef() []*Lease + AddStaticLease(lease Lease) error + RemoveStaticLease(l Lease) error + FindMACbyIP(ip net.IP) net.HardwareAddr + + WriteDiskConfig4(c *V4ServerConf) + WriteDiskConfig6(c *V6ServerConf) + + Start() error + Stop() + Reset() +} + +// V4ServerConf - server configuration +type V4ServerConf struct { + Enabled bool `yaml:"enabled"` + InterfaceName string `yaml:"interface_name"` // eth0, en0 and so on + GatewayIP string `yaml:"gateway_ip"` + SubnetMask string `yaml:"subnet_mask"` + RangeStart string `yaml:"range_start"` + RangeEnd string `yaml:"range_end"` + LeaseDuration uint32 `yaml:"lease_duration"` // in seconds + + // IP conflict detector: time (ms) to wait for ICMP reply. + // 0: disable + ICMPTimeout uint32 `yaml:"icmp_timeout_msec"` + + ipStart net.IP + ipEnd net.IP + leaseTime time.Duration + dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses + routerIP net.IP + subnetMask net.IPMask + + notify func(uint32) +} + +// V6ServerConf - server configuration +type V6ServerConf struct { + Enabled bool `yaml:"enabled"` + InterfaceName string `yaml:"interface_name"` + RangeStart string `yaml:"range_start"` + LeaseDuration uint32 `yaml:"lease_duration"` // in seconds + + ipStart net.IP + leaseTime time.Duration + dnsIPAddrs []net.IP // IPv6 addresses to return to DHCP clients as DNS server addresses + + notify func(uint32) +} diff --git a/dhcpd/v4.go b/dhcpd/v4.go index a15600f8..0bfa6aa8 100644 --- a/dhcpd/v4.go +++ b/dhcpd/v4.go @@ -1,3 +1,5 @@ +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + package dhcpd import ( @@ -14,8 +16,8 @@ import ( "github.com/sparrc/go-ping" ) -// V4Server - DHCPv4 server -type V4Server struct { +// v4Server - DHCPv4 server +type v4Server struct { srv *server4.Server leasesLock sync.Mutex leases []*Lease @@ -24,37 +26,20 @@ type V4Server struct { conf V4ServerConf } -// V4ServerConf - server configuration -type V4ServerConf struct { - Enabled bool `json:"enabled" yaml:"enabled"` - InterfaceName string `json:"interface_name" yaml:"interface_name"` // eth0, en0 and so on - GatewayIP string `json:"gateway_ip" yaml:"gateway_ip"` - SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` - RangeStart string `json:"range_start" yaml:"range_start"` - RangeEnd string `json:"range_end" yaml:"range_end"` - LeaseDuration uint32 `json:"lease_duration" yaml:"lease_duration"` // in seconds - - // IP conflict detector: time (ms) to wait for ICMP reply. - // 0: disable - ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` - - ipStart net.IP - ipEnd net.IP - leaseTime time.Duration - dnsIPAddrs []net.IP // IPv4 addresses to return to DHCP clients as DNS server addresses - routerIP net.IP - subnetMask net.IPMask - - notify func(uint32) +// WriteDiskConfig4 - write configuration +func (s *v4Server) WriteDiskConfig4(c *V4ServerConf) { + *c = s.conf } -// WriteDiskConfig - write configuration -func (s *V4Server) WriteDiskConfig(c *V4ServerConf) { - *c = s.conf +// WriteDiskConfig6 - write configuration +func (s *v4Server) WriteDiskConfig6(c *V6ServerConf) { } // Return TRUE if IP address is within range [start..stop] func ipInRange(start net.IP, stop net.IP, ip net.IP) bool { + if len(start) != 4 || len(stop) != 4 { + return false + } from := binary.BigEndian.Uint32(start) to := binary.BigEndian.Uint32(stop) check := binary.BigEndian.Uint32(ip) @@ -62,7 +47,7 @@ func ipInRange(start net.IP, stop net.IP, ip net.IP) bool { } // ResetLeases - reset leases -func (s *V4Server) ResetLeases(leases []*Lease) { +func (s *v4Server) ResetLeases(leases []*Lease) { s.leases = nil for _, l := range leases { @@ -79,12 +64,12 @@ func (s *V4Server) ResetLeases(leases []*Lease) { } // GetLeasesRef - get leases -func (s *V4Server) GetLeasesRef() []*Lease { +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 { +func (s *v4Server) GetLeases(flags int) []Lease { var result []Lease now := time.Now().Unix() @@ -101,7 +86,7 @@ func (s *V4Server) GetLeases(flags int) []Lease { } // FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases -func (s *V4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { +func (s *v4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { now := time.Now().Unix() s.leasesLock.Lock() @@ -124,7 +109,7 @@ func (s *V4Server) FindMACbyIP(ip net.IP) net.HardwareAddr { } // Add the specified IP to the black list for a time period -func (s *V4Server) blacklistLease(lease *Lease) { +func (s *v4Server) blacklistLease(lease *Lease) { hw := make(net.HardwareAddr, 6) lease.HWAddr = hw lease.Hostname = "" @@ -132,7 +117,7 @@ func (s *V4Server) blacklistLease(lease *Lease) { } // Remove (swap) lease by index -func (s *V4Server) leaseRemoveSwapByIndex(i int) { +func (s *v4Server) leaseRemoveSwapByIndex(i int) { s.ipAddrs[s.leases[i].IP[3]] = 0 log.Debug("DHCPv4: removed lease %s", s.leases[i].HWAddr) @@ -145,7 +130,7 @@ func (s *V4Server) leaseRemoveSwapByIndex(i int) { // Remove a dynamic lease with the same properties // Return error if a static lease is found -func (s *V4Server) rmDynamicLease(lease Lease) error { +func (s *v4Server) rmDynamicLease(lease Lease) error { for i := 0; i < len(s.leases); i++ { l := s.leases[i] @@ -172,14 +157,14 @@ func (s *V4Server) rmDynamicLease(lease Lease) error { } // Add a lease -func (s *V4Server) addLease(l *Lease) { +func (s *v4Server) addLease(l *Lease) { s.leases = append(s.leases, l) s.ipAddrs[l.IP[3]] = 1 log.Debug("DHCPv4: added lease %s <-> %s", l.IP, l.HWAddr) } // 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 { if net.IP.Equal(l.IP, lease.IP) { @@ -197,7 +182,7 @@ func (s *V4Server) rmLease(lease Lease) error { } // AddStaticLease adds a static lease (thread-safe) -func (s *V4Server) AddStaticLease(lease Lease) error { +func (s *v4Server) AddStaticLease(lease Lease) error { if len(lease.IP) != 4 { return fmt.Errorf("invalid IP") } @@ -221,7 +206,7 @@ func (s *V4Server) AddStaticLease(lease Lease) error { } // RemoveStaticLease removes a static lease (thread-safe) -func (s *V4Server) RemoveStaticLease(l Lease) error { +func (s *v4Server) RemoveStaticLease(l Lease) error { if len(l.IP) != 4 { return fmt.Errorf("invalid IP") } @@ -244,7 +229,7 @@ func (s *V4Server) RemoveStaticLease(l Lease) error { // Send ICMP to the specified machine // Return TRUE if it doesn't reply, which probably means that the IP is available -func (s *V4Server) addrAvailable(target net.IP) bool { +func (s *v4Server) addrAvailable(target net.IP) bool { if s.conf.ICMPTimeout == 0 { return true @@ -276,7 +261,7 @@ func (s *V4Server) addrAvailable(target net.IP) bool { } // Find lease by MAC -func (s *V4Server) findLease(mac net.HardwareAddr) *Lease { +func (s *v4Server) findLease(mac net.HardwareAddr) *Lease { for i := range s.leases { if bytes.Equal(mac, s.leases[i].HWAddr) { return s.leases[i] @@ -286,7 +271,7 @@ func (s *V4Server) findLease(mac net.HardwareAddr) *Lease { } // Get next free IP -func (s *V4Server) findFreeIP() net.IP { +func (s *v4Server) findFreeIP() net.IP { for i := s.conf.ipStart[3]; ; i++ { if s.ipAddrs[i] == 0 { ip := make([]byte, 4) @@ -302,7 +287,7 @@ func (s *V4Server) findFreeIP() net.IP { } // Find an expired lease and return its index or -1 -func (s *V4Server) findExpiredLease() int { +func (s *v4Server) findExpiredLease() int { now := time.Now().Unix() for i, lease := range s.leases { if lease.Expiry.Unix() != leaseExpireStatic && @@ -314,7 +299,7 @@ func (s *V4Server) findExpiredLease() int { } // Reserve lease for MAC -func (s *V4Server) reserveLease(mac net.HardwareAddr) *Lease { +func (s *v4Server) reserveLease(mac net.HardwareAddr) *Lease { l := Lease{} l.HWAddr = make([]byte, 6) copy(l.HWAddr, mac) @@ -334,7 +319,7 @@ func (s *V4Server) reserveLease(mac net.HardwareAddr) *Lease { } // Process Discover request and return lease -func (s *V4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lease { +func (s *v4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lease { mac := req.ClientHWAddr s.leasesLock.Lock() @@ -384,7 +369,7 @@ func (s *V4Server) processDiscover(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) *Lea // 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) { +func (s *v4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lease, bool) { var lease *Lease mac := req.ClientHWAddr hostname := req.Options.Get(dhcpv4.OptionHostName) @@ -450,7 +435,7 @@ func (s *V4Server) processRequest(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) (*Lea // 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 @@ -489,7 +474,7 @@ func (s *V4Server) process(req *dhcpv4.DHCPv4, resp *dhcpv4.DHCPv4) int { // client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,Offer,ServerID,SubnetMask,LeaseTime) <- server(:67) // client(0.0.0.0:68) -> (Request:ClientMAC,Request,ClientID,ReqIP,HostName,ServerID,ParamReqList) -> server(255.255.255.255:67) // client(255.255.255.255:68) <- (Reply:YourIP,ClientMAC,ACK,ServerID,SubnetMask,LeaseTime) <- server(:67) -func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) { +func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) { log.Debug("DHCPv4: received message: %s", req.Summary()) switch req.MessageType() { @@ -529,28 +514,8 @@ func (s *V4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4 } } -// Get IPv4 address list -func getIfaceIPv4(iface net.Interface) []net.IP { - addrs, err := iface.Addrs() - if err != nil { - return nil - } - - var res []net.IP - for _, a := range addrs { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - if ipnet.IP.To4() != nil { - res = append(res, ipnet.IP.To4()) - } - } - return res -} - // Start - start server -func (s *V4Server) Start() error { +func (s *v4Server) Start() error { if !s.conf.Enabled { return nil } @@ -586,14 +551,14 @@ func (s *V4Server) Start() error { } // Reset - stop server -func (s *V4Server) Reset() { +func (s *v4Server) Reset() { s.leasesLock.Lock() s.leases = nil s.leasesLock.Unlock() } // Stop - stop server -func (s *V4Server) Stop() { +func (s *v4Server) Stop() { if s.srv == nil { return } @@ -605,9 +570,9 @@ func (s *V4Server) Stop() { // now server.Serve() will return } -// Create DHCPv6 server -func v4Create(conf V4ServerConf) (*V4Server, error) { - s := &V4Server{} +// Create DHCPv4 server +func v4Create(conf V4ServerConf) (DHCPServer, error) { + s := &v4Server{} s.conf = conf if !conf.Enabled { diff --git a/dhcpd/v46_windows.go b/dhcpd/v46_windows.go new file mode 100644 index 00000000..bf3c2781 --- /dev/null +++ b/dhcpd/v46_windows.go @@ -0,0 +1,45 @@ +package dhcpd + +import "net" + +type winServer struct { +} + +func (s *winServer) ResetLeases(leases []*Lease) { +} +func (s *winServer) GetLeases(flags int) []Lease { + return nil +} +func (s *winServer) GetLeasesRef() []*Lease { + return nil +} +func (s *winServer) AddStaticLease(lease Lease) error { + return nil +} +func (s *winServer) RemoveStaticLease(l Lease) error { + return nil +} +func (s *winServer) FindMACbyIP(ip net.IP) net.HardwareAddr { + return nil +} + +func (s *winServer) WriteDiskConfig4(c *V4ServerConf) { +} +func (s *winServer) WriteDiskConfig6(c *V6ServerConf) { +} + +func (s *winServer) Start() error { + return nil +} +func (s *winServer) Stop() { +} +func (s *winServer) Reset() { +} + +func v4Create(conf V4ServerConf) (DHCPServer, error) { + return &winServer{}, nil +} + +func v6Create(conf V6ServerConf) (DHCPServer, error) { + return &winServer{}, nil +} diff --git a/dhcpd/v4_test.go b/dhcpd/v4_test.go new file mode 100644 index 00000000..68ffb88f --- /dev/null +++ b/dhcpd/v4_test.go @@ -0,0 +1,224 @@ +package dhcpd + +import ( + "net" + "testing" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/stretchr/testify/assert" +) + +func notify4(flags uint32) { +} + +func TestV4StaticLeaseAddRemove(t *testing.T) { + conf := V4ServerConf{ + Enabled: true, + RangeStart: "192.168.10.100", + RangeEnd: "192.168.10.200", + GatewayIP: "192.168.10.1", + SubnetMask: "255.255.255.0", + notify: notify4, + } + s, err := v4Create(conf) + assert.True(t, err == nil) + + ls := s.GetLeases(LeasesStatic) + assert.Equal(t, 0, len(ls)) + + // add static lease + l := Lease{} + l.IP = net.ParseIP("192.168.10.150").To4() + l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // try to add the same static lease - fail + assert.True(t, s.AddStaticLease(l) != nil) + + // check + ls = s.GetLeases(LeasesStatic) + assert.Equal(t, 1, len(ls)) + assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + + // try to remove static lease - fail + l.IP = net.ParseIP("192.168.10.110").To4() + l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + assert.True(t, s.RemoveStaticLease(l) != nil) + + // remove static lease + l.IP = net.ParseIP("192.168.10.150").To4() + l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + assert.True(t, s.RemoveStaticLease(l) == nil) + + // check + ls = s.GetLeases(LeasesStatic) + assert.Equal(t, 0, len(ls)) +} + +func TestV4StaticLeaseAddReplaceDynamic(t *testing.T) { + conf := V4ServerConf{ + Enabled: true, + RangeStart: "192.168.10.100", + RangeEnd: "192.168.10.200", + GatewayIP: "192.168.10.1", + SubnetMask: "255.255.255.0", + notify: notify4, + } + s, err := v4Create(conf) + assert.True(t, err == nil) + + // add dynamic lease + ld := Lease{} + ld.IP = net.ParseIP("192.168.10.150").To4() + ld.HWAddr, _ = net.ParseMAC("11:aa:aa:aa:aa:aa") + s.addLease(&ld) + + // add dynamic lease + { + ld := Lease{} + ld.IP = net.ParseIP("192.168.10.151").To4() + ld.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") + s.addLease(&ld) + } + + // add static lease with the same IP + l := Lease{} + l.IP = net.ParseIP("192.168.10.150").To4() + l.HWAddr, _ = net.ParseMAC("33:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // add static lease with the same MAC + l = Lease{} + l.IP = net.ParseIP("192.168.10.152").To4() + l.HWAddr, _ = net.ParseMAC("22:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // check + ls := s.GetLeases(LeasesStatic) + assert.Equal(t, 2, len(ls)) + + assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.Equal(t, "33:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + assert.True(t, ls[0].Expiry.Unix() == leaseExpireStatic) + + assert.Equal(t, "192.168.10.152", ls[1].IP.String()) + assert.Equal(t, "22:aa:aa:aa:aa:aa", ls[1].HWAddr.String()) + assert.True(t, ls[1].Expiry.Unix() == leaseExpireStatic) +} + +func TestV4StaticLeaseGet(t *testing.T) { + conf := V4ServerConf{ + Enabled: true, + RangeStart: "192.168.10.100", + RangeEnd: "192.168.10.200", + GatewayIP: "192.168.10.1", + SubnetMask: "255.255.255.0", + notify: notify4, + } + s, err := v4Create(conf) + assert.True(t, err == nil) + s.conf.dnsIPAddrs = []net.IP{net.ParseIP("192.168.10.1").To4()} + + l := Lease{} + l.IP = net.ParseIP("192.168.10.150").To4() + l.HWAddr, _ = net.ParseMAC("aa:aa:aa:aa:aa:aa") + assert.True(t, s.AddStaticLease(l) == nil) + + // "Discover" + mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") + req, _ := dhcpv4.NewDiscovery(mac) + resp, _ := dhcpv4.NewReplyFromRequest(req) + assert.Equal(t, 1, s.process(req, resp)) + + // check "Offer" + assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) + assert.Equal(t, "192.168.10.150", resp.YourIPAddr.String()) + assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) + assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) + assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + + // "Request" + req, _ = dhcpv4.NewRequestFromOffer(resp) + resp, _ = dhcpv4.NewReplyFromRequest(req) + assert.Equal(t, 1, s.process(req, resp)) + + // check "Ack" + assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) + assert.Equal(t, "192.168.10.150", resp.YourIPAddr.String()) + assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) + assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) + assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + + dnsAddrs := resp.DNS() + assert.Equal(t, 1, len(dnsAddrs)) + assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) + + // check lease + ls := s.GetLeases(LeasesStatic) + assert.Equal(t, 1, len(ls)) + assert.Equal(t, "192.168.10.150", ls[0].IP.String()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + + s.Stop() +} + +func TestV4DynamicLeaseGet(t *testing.T) { + conf := V4ServerConf{ + Enabled: true, + RangeStart: "192.168.10.100", + RangeEnd: "192.168.10.200", + GatewayIP: "192.168.10.1", + SubnetMask: "255.255.255.0", + notify: notify4, + } + s, err := v4Create(conf) + assert.True(t, err == nil) + s.conf.dnsIPAddrs = []net.IP{net.ParseIP("192.168.10.1").To4()} + + // "Discover" + mac, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa") + req, _ := dhcpv4.NewDiscovery(mac) + resp, _ := dhcpv4.NewReplyFromRequest(req) + assert.Equal(t, 1, s.process(req, resp)) + + // check "Offer" + assert.Equal(t, dhcpv4.MessageTypeOffer, resp.MessageType()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) + assert.Equal(t, "192.168.10.100", resp.YourIPAddr.String()) + assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) + assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) + assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + + // "Request" + req, _ = dhcpv4.NewRequestFromOffer(resp) + resp, _ = dhcpv4.NewReplyFromRequest(req) + assert.Equal(t, 1, s.process(req, resp)) + + // check "Ack" + assert.Equal(t, dhcpv4.MessageTypeAck, resp.MessageType()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", resp.ClientHWAddr.String()) + assert.Equal(t, "192.168.10.100", resp.YourIPAddr.String()) + assert.Equal(t, "192.168.10.1", resp.Router()[0].String()) + assert.Equal(t, "192.168.10.1", resp.ServerIdentifier().String()) + assert.Equal(t, "255.255.255.0", net.IP(resp.SubnetMask()).String()) + assert.Equal(t, s.conf.leaseTime.Seconds(), resp.IPAddressLeaseTime(-1).Seconds()) + + dnsAddrs := resp.DNS() + assert.Equal(t, 1, len(dnsAddrs)) + assert.Equal(t, "192.168.10.1", dnsAddrs[0].String()) + + // check lease + ls := s.GetLeases(LeasesDynamic) + assert.Equal(t, 1, len(ls)) + assert.Equal(t, "192.168.10.100", ls[0].IP.String()) + assert.Equal(t, "aa:aa:aa:aa:aa:aa", ls[0].HWAddr.String()) + + s.Stop() +} diff --git a/dhcpd/v6.go b/dhcpd/v6.go index 33a570f1..d2a3eae6 100644 --- a/dhcpd/v6.go +++ b/dhcpd/v6.go @@ -1,3 +1,5 @@ +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + package dhcpd import ( @@ -15,38 +17,28 @@ import ( const valueIAID = "ADGH" // value for IANA.ID -// V6Server - DHCPv6 server -type V6Server struct { +// v6Server - DHCPv6 server +type v6Server struct { srv *server6.Server leasesLock sync.Mutex leases []*Lease ipAddrs [256]byte + sid dhcpv6.Duid conf V6ServerConf } -// V6ServerConf - server configuration -type V6ServerConf struct { - Enabled bool `yaml:"enabled"` - InterfaceName string `yaml:"interface_name"` - RangeStart string `yaml:"range_start"` - LeaseDuration uint32 `yaml:"lease_duration"` // in seconds - - ipStart net.IP - leaseTime time.Duration - dnsIPAddrs []net.IP // IPv6 addresses to return to DHCP clients as DNS server addresses - sid dhcpv6.Duid - - notify func(uint32) +// WriteDiskConfig4 - write configuration +func (s *v6Server) WriteDiskConfig4(c *V4ServerConf) { } -// WriteDiskConfig - write configuration -func (s *V6Server) WriteDiskConfig(c *V6ServerConf) { +// WriteDiskConfig6 - write configuration +func (s *v6Server) WriteDiskConfig6(c *V6ServerConf) { *c = s.conf } // ResetLeases - reset leases -func (s *V6Server) ResetLeases(ll []*Lease) { +func (s *v6Server) ResetLeases(ll []*Lease) { s.leases = nil for _, l := range ll { // TODO @@ -55,7 +47,7 @@ func (s *V6Server) ResetLeases(ll []*Lease) { } // GetLeases - get current leases -func (s *V6Server) GetLeases(flags int) []Lease { +func (s *v6Server) GetLeases(flags int) []Lease { var result []Lease s.leasesLock.Lock() for _, lease := range s.leases { @@ -76,12 +68,12 @@ func (s *V6Server) GetLeases(flags int) []Lease { } // GetLeasesRef - get leases -func (s *V6Server) GetLeasesRef() []*Lease { +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 { +func (s *v6Server) FindMACbyIP(ip net.IP) net.HardwareAddr { now := time.Now().Unix() s.leasesLock.Lock() @@ -104,7 +96,7 @@ func (s *V6Server) FindMACbyIP(ip net.IP) net.HardwareAddr { } // Remove (swap) lease by index -func (s *V6Server) leaseRemoveSwapByIndex(i int) { +func (s *v6Server) leaseRemoveSwapByIndex(i int) { s.ipAddrs[s.leases[i].IP[15]] = 0 log.Debug("DHCPv6: removed lease %s", s.leases[i].HWAddr) @@ -117,7 +109,7 @@ func (s *V6Server) leaseRemoveSwapByIndex(i int) { // Remove a dynamic lease with the same properties // Return error if a static lease is found -func (s *V6Server) rmDynamicLease(lease Lease) error { +func (s *v6Server) rmDynamicLease(lease Lease) error { for i := 0; i < len(s.leases); i++ { l := s.leases[i] @@ -144,7 +136,7 @@ func (s *V6Server) rmDynamicLease(lease Lease) error { } // AddStaticLease - add a static lease -func (s *V6Server) AddStaticLease(l Lease) error { +func (s *v6Server) AddStaticLease(l Lease) error { if len(l.IP) != 16 { return fmt.Errorf("invalid IP") } @@ -169,7 +161,7 @@ func (s *V6Server) AddStaticLease(l Lease) error { } // RemoveStaticLease - remove a static lease -func (s *V6Server) RemoveStaticLease(l Lease) error { +func (s *v6Server) RemoveStaticLease(l Lease) error { if len(l.IP) != 16 { return fmt.Errorf("invalid IP") } @@ -190,14 +182,14 @@ func (s *V6Server) RemoveStaticLease(l Lease) error { } // Add a lease -func (s *V6Server) addLease(l *Lease) { +func (s *v6Server) addLease(l *Lease) { s.leases = append(s.leases, l) s.ipAddrs[l.IP[15]] = 1 log.Debug("DHCPv6: added lease %s <-> %s", l.IP, l.HWAddr) } // 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 { if net.IP.Equal(l.IP, lease.IP) { @@ -215,7 +207,7 @@ func (s *V6Server) rmLease(lease Lease) error { } // Find lease by MAC -func (s *V6Server) findLease(mac net.HardwareAddr) *Lease { +func (s *v6Server) findLease(mac net.HardwareAddr) *Lease { s.leasesLock.Lock() defer s.leasesLock.Unlock() @@ -228,7 +220,7 @@ func (s *V6Server) findLease(mac net.HardwareAddr) *Lease { } // Find an expired lease and return its index or -1 -func (s *V6Server) findExpiredLease() int { +func (s *v6Server) findExpiredLease() int { now := time.Now().Unix() for i, lease := range s.leases { if lease.Expiry.Unix() != leaseExpireStatic && @@ -240,7 +232,7 @@ func (s *V6Server) findExpiredLease() int { } // Get next free IP -func (s *V6Server) findFreeIP() net.IP { +func (s *v6Server) findFreeIP() net.IP { for i := s.conf.ipStart[15]; ; i++ { if s.ipAddrs[i] == 0 { ip := make([]byte, 16) @@ -256,7 +248,7 @@ func (s *V6Server) findFreeIP() net.IP { } // Reserve lease for MAC -func (s *V6Server) reserveLease(mac net.HardwareAddr) *Lease { +func (s *v6Server) reserveLease(mac net.HardwareAddr) *Lease { l := Lease{} l.HWAddr = make([]byte, 6) copy(l.HWAddr, mac) @@ -280,7 +272,7 @@ func (s *V6Server) reserveLease(mac net.HardwareAddr) *Lease { } // Check Client ID -func (s *V6Server) checkCID(msg *dhcpv6.Message) error { +func (s *v6Server) checkCID(msg *dhcpv6.Message) error { if msg.Options.ClientID() == nil { return fmt.Errorf("DHCPv6: no ClientID option in request") } @@ -288,7 +280,7 @@ func (s *V6Server) checkCID(msg *dhcpv6.Message) error { } // Check ServerID policy -func (s *V6Server) checkSID(msg *dhcpv6.Message) error { +func (s *v6Server) checkSID(msg *dhcpv6.Message) error { sid := msg.Options.ServerID() switch msg.Type() { @@ -308,7 +300,7 @@ func (s *V6Server) checkSID(msg *dhcpv6.Message) error { if sid == nil { return fmt.Errorf("DHCPv6: drop packet: no ServerID option in message %s", msg.Type().String()) } - if !sid.Equal(s.conf.sid) { + if !sid.Equal(s.sid) { return fmt.Errorf("DHCPv6: drop packet: mismatched ServerID option in message %s: %s", msg.Type().String(), sid.String()) } @@ -319,7 +311,7 @@ func (s *V6Server) checkSID(msg *dhcpv6.Message) error { // . IAID must be equal to this server's ID // . IAAddress must be equal to the lease's IP -func (s *V6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error { +func (s *v6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error { switch msg.Type() { case dhcpv6.MessageTypeRequest, dhcpv6.MessageTypeConfirm, @@ -348,7 +340,7 @@ func (s *V6Server) checkIA(msg *dhcpv6.Message, lease *Lease) error { } // Store lease in DB (if necessary) and return lease life time -func (s *V6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration { +func (s *v6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration { lifetime := s.conf.leaseTime switch msg.Type() { @@ -376,7 +368,7 @@ func (s *V6Server) commitLease(msg *dhcpv6.Message, lease *Lease) time.Duration } // Find a lease associated with MAC and prepare response -func (s *V6Server) process(msg *dhcpv6.Message, req dhcpv6.DHCPv6, resp dhcpv6.DHCPv6) bool { +func (s *v6Server) process(msg *dhcpv6.Message, req dhcpv6.DHCPv6, resp dhcpv6.DHCPv6) bool { switch msg.Type() { case dhcpv6.MessageTypeSolicit, dhcpv6.MessageTypeRequest, @@ -452,7 +444,7 @@ func (s *V6Server) process(msg *dhcpv6.Message, req dhcpv6.DHCPv6, resp dhcpv6.D // // 3. // fe80::* --(Release + ClientID+ServerID+IANA(IAAddress))-> ff02::1:2 -func (s *V6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.DHCPv6) { +func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.DHCPv6) { msg, err := req.GetInnerMessage() if err != nil { log.Error("DHCPv6: %s", err) @@ -501,7 +493,7 @@ func (s *V6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6. return } - resp.AddOption(dhcpv6.OptServerID(s.conf.sid)) + resp.AddOption(dhcpv6.OptServerID(s.sid)) _ = s.process(msg, req, resp) @@ -535,7 +527,7 @@ func getIfaceIPv6(iface net.Interface) []net.IP { } // Start - start server -func (s *V6Server) Start() error { +func (s *v6Server) Start() error { if !s.conf.Enabled { return nil } @@ -558,7 +550,7 @@ func (s *V6Server) Start() error { if len(iface.HardwareAddr) != 6 { return fmt.Errorf("DHCPv6: invalid MAC %s", iface.HardwareAddr) } - s.conf.sid = dhcpv6.Duid{ + s.sid = dhcpv6.Duid{ Type: dhcpv6.DUID_LLT, HwType: iana.HWTypeEthernet, LinkLayerAddr: iface.HardwareAddr, @@ -581,14 +573,14 @@ func (s *V6Server) Start() error { } // Reset - stop server -func (s *V6Server) Reset() { +func (s *v6Server) Reset() { s.leasesLock.Lock() s.leases = nil s.leasesLock.Unlock() } // Stop - stop server -func (s *V6Server) Stop() { +func (s *v6Server) Stop() { if s.srv == nil { return } @@ -601,8 +593,8 @@ func (s *V6Server) Stop() { } // Create DHCPv6 server -func v6Create(conf V6ServerConf) (*V6Server, error) { - s := &V6Server{} +func v6Create(conf V6ServerConf) (DHCPServer, error) { + s := &v6Server{} s.conf = conf if !conf.Enabled { diff --git a/home/clients.go b/home/clients.go index 90750776..1c1719c5 100644 --- a/home/clients.go +++ b/home/clients.go @@ -84,6 +84,7 @@ type clientsContainer struct { } // Init initializes clients container +// dhcpServer: optional // Note: this function must be called only once func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd.Server, autoHosts *util.AutoHosts) { if clients.list != nil { @@ -104,7 +105,9 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd. if !clients.testing { clients.addFromDHCP() - clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged) + if clients.dhcpServer != nil { + clients.dhcpServer.SetOnLeaseChanged(clients.onDHCPLeaseChanged) + } clients.autoHosts.SetOnChanged(clients.onHostsChanged) } }