From 8aa30a8e83d70a684748cb5068ec97768d0dd061 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Mon, 27 Apr 2020 10:46:11 +0300 Subject: [PATCH] + dhcpv6 server; support static leases --- dhcpd/db.go | 30 +++++++++++- dhcpd/dhcp_http.go | 29 ++++++++++- dhcpd/dhcpd.go | 12 +++++ dhcpd/v6.go | 119 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 5 ++ 6 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 dhcpd/v6.go diff --git a/dhcpd/db.go b/dhcpd/db.go index fdf94059..36893afa 100644 --- a/dhcpd/db.go +++ b/dhcpd/db.go @@ -46,6 +46,7 @@ func (s *Server) dbLoad() { s.IPpool = make(map[[4]byte]net.HardwareAddr) dynLeases := []*Lease{} staticLeases := []*Lease{} + v6StaticLeases := []*Lease{} data, err := ioutil.ReadFile(s.conf.DBFilePath) if err != nil { @@ -66,7 +67,13 @@ func (s *Server) dbLoad() { for i := range obj { obj[i].IP = normalizeIP(obj[i].IP) + if !(len(obj[i].IP) == 4 || len(obj[i].IP) == 16) { + log.Info("DHCP: invalid IP: %s", obj[i].IP) + continue + } + if obj[i].Expiry != leaseExpireStatic && + len(obj[i].IP) == 4 && !ipInRange(s.leaseStart, s.leaseStop, obj[i].IP) { log.Tracef("Skipping a lease with IP %v: not within current IP range", obj[i].IP) @@ -80,7 +87,10 @@ func (s *Server) dbLoad() { Expiry: time.Unix(obj[i].Expiry, 0), } - if obj[i].Expiry == leaseExpireStatic { + if len(obj[i].IP) == 16 { + v6StaticLeases = append(v6StaticLeases, &lease) + + } else if obj[i].Expiry == leaseExpireStatic { staticLeases = append(staticLeases, &lease) } else { dynLeases = append(dynLeases, &lease) @@ -93,7 +103,10 @@ func (s *Server) dbLoad() { s.reserveIP(lease.IP, lease.HWAddr) } - log.Info("DHCP: loaded %d (%d) leases from DB", len(s.leases), numLeases) + s.v6Leases = normalizeLeases(v6StaticLeases, []*Lease{}) + + log.Info("DHCP: loaded leases v4:%d v6:%d total-read:%d from DB", + len(s.leases), len(s.v6Leases), numLeases) } // Skip duplicate leases @@ -140,6 +153,19 @@ func (s *Server) dbStore() { leases = append(leases, lease) } + for _, l := range s.v6Leases { + if l.Expiry.Unix() == 0 { + continue + } + lease := leaseJSON{ + HWAddr: l.HWAddr, + IP: l.IP, + Hostname: l.Hostname, + Expiry: l.Expiry.Unix(), + } + leases = append(leases, lease) + } + data, err := json.Marshal(leases) if err != nil { log.Error("json.Marshal: %v", err) diff --git a/dhcpd/dhcp_http.go b/dhcpd/dhcp_http.go index 9105d76b..f2ec49c0 100644 --- a/dhcpd/dhcp_http.go +++ b/dhcpd/dhcp_http.go @@ -246,13 +246,38 @@ func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request return } - ip, _ := parseIPv4(lj.IP) + ip := net.ParseIP(lj.IP) + if ip != nil && ip.To16() != nil { + mac, err := net.ParseMAC(lj.HWAddr) + if err != nil { + httpError(r, w, http.StatusBadRequest, "invalid MAC") + return + } + + lease := Lease{ + IP: ip, + HWAddr: mac, + } + + err = s.v6AddStaticLease(lease) + if err != nil { + httpError(r, w, http.StatusBadRequest, "%s", err) + return + } + return + } + + ip, _ = parseIPv4(lj.IP) if ip == nil { httpError(r, w, http.StatusBadRequest, "invalid IP") return } - mac, _ := net.ParseMAC(lj.HWAddr) + mac, err := net.ParseMAC(lj.HWAddr) + if err != nil { + httpError(r, w, http.StatusBadRequest, "invalid MAC") + return + } lease := Lease{ IP: ip, diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index f95a9867..8ab41d36 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -47,6 +47,8 @@ type ServerConfig struct { // 0: disable ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` + EnableV6 bool `yaml:"enable_v6"` + WorkDir string `json:"-" yaml:"-"` DBFilePath string `json:"-" yaml:"-"` // path to DB file @@ -89,6 +91,9 @@ type Server struct { // IP address pool -- if entry is in the pool, then it's attached to a lease IPpool map[[4]byte]net.HardwareAddr + v6Leases []*Lease + v6LeasesLock sync.RWMutex + conf ServerConfig // Called when the leases DB is modified @@ -255,6 +260,13 @@ func (s *Server) Start() error { s.cond.Signal() }() + if s.conf.EnableV6 { + err := s.v6Start() + if err != nil { + return err + } + } + return nil } diff --git a/dhcpd/v6.go b/dhcpd/v6.go new file mode 100644 index 00000000..f4a63154 --- /dev/null +++ b/dhcpd/v6.go @@ -0,0 +1,119 @@ +package dhcpd + +import ( + "bytes" + "fmt" + "net" + "time" + + "github.com/AdguardTeam/golibs/log" + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/dhcpv6/server6" +) + +const valIAID = "ADGH" + +func (s *Server) v6AddStaticLease(l Lease) error { + l.Expiry = time.Unix(leaseExpireStatic, 0) + + s.v6LeasesLock.Lock() + s.v6Leases = append(s.v6Leases, &l) + s.dbStore() + s.v6LeasesLock.Unlock() + // s.notify(LeaseChangedAddedStatic) + return nil +} + +func (s *Server) v6FindLease(mac net.HardwareAddr) *Lease { + s.v6LeasesLock.Lock() + defer s.v6LeasesLock.Unlock() + + for i := range s.v6Leases { + if bytes.Equal(mac, s.v6Leases[i].HWAddr) { + return s.v6Leases[i] + } + } + return nil +} + +func (s *Server) v6Process(req dhcpv6.DHCPv6, resp dhcpv6.DHCPv6) { + mac, err := dhcpv6.ExtractMAC(req) + if err != nil { + log.Debug("DHCPv6: dhcpv6.ExtractMAC: %s", err) + return + } + + lease := s.v6FindLease(mac) + if lease == nil { + log.Debug("DHCPv6: no lease for: %s", mac) + return + } + + oia := &dhcpv6.OptIANA{} + copy(oia.IaId[:], []byte(valIAID)) + oia.Options = dhcpv6.IdentityOptions{Options: []dhcpv6.Option{ + &dhcpv6.OptIAAddress{ + IPv6Addr: lease.IP, + PreferredLifetime: s.leaseTime, + ValidLifetime: s.leaseTime, + }, + }} + resp.AddOption(oia) +} + +func (s *Server) v6PacketHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.DHCPv6) { + msg, err := req.GetInnerMessage() + if err != nil { + log.Error("DHCPv6: %s", err) + return + } + + var resp dhcpv6.DHCPv6 + + switch msg.Type() { + case dhcpv6.MessageTypeSolicit: + if msg.GetOneOption(dhcpv6.OptionRapidCommit) != nil { + resp, err = dhcpv6.NewReplyFromMessage(msg) + } else { + resp, err = dhcpv6.NewAdvertiseFromSolicit(msg) + } + + case dhcpv6.MessageTypeRequest, + dhcpv6.MessageTypeConfirm, + dhcpv6.MessageTypeRenew, + dhcpv6.MessageTypeRebind, + dhcpv6.MessageTypeRelease, + dhcpv6.MessageTypeInformationRequest: + resp, err = dhcpv6.NewReplyFromMessage(msg) + + default: + err = fmt.Errorf("message type %d not supported", msg.Type()) + } + + if err != nil { + log.Error("DHCPv6: %s", err) + return + } + + s.v6Process(req, resp) + + _, err = conn.WriteTo(resp.ToBytes(), peer) + if err != nil { + log.Error("DHCPv6: conn.Write to %s failed: %s", peer, err) + return + } +} + +func (s *Server) v6Start() error { + laddr := &net.UDPAddr{ + IP: net.ParseIP("::1"), + Port: dhcpv6.DefaultServerPort, + } + server, err := server6.NewServer("", laddr, s.v6PacketHandler, server6.WithDebugLogger()) + if err != nil { + log.Fatal(err) + } + + server.Serve() + return nil +} diff --git a/go.mod b/go.mod index f2d7aa1c..42a8bd08 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/NYTimes/gziphandler v1.1.1 github.com/fsnotify/fsnotify v1.4.7 github.com/gobuffalo/packr v1.30.1 + github.com/insomniacslk/dhcp v0.0.0-20200420235442-ed3125c2efe7 github.com/joomcode/errorx v1.0.1 github.com/kardianos/service v1.0.0 github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 @@ -16,6 +17,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c github.com/stretchr/testify v1.5.1 + github.com/u-root/u-root v6.0.0+incompatible // indirect go.etcd.io/bbolt v1.3.4 golang.org/x/crypto v0.0.0-20200403201458-baeed622b8d8 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e diff --git a/go.sum b/go.sum index d5fef41f..a11a2d5b 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,8 @@ github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIavi github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/insomniacslk/dhcp v0.0.0-20200420235442-ed3125c2efe7 h1:iaCm+9nZdYb8XCSU2TfIb0qYTcAlIv2XzyKR2d2xZ38= +github.com/insomniacslk/dhcp v0.0.0-20200420235442-ed3125c2efe7/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= @@ -100,6 +102,7 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -107,6 +110,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE= +github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=