From 5b774cf90ec7bb5a8ed9cba4e8ff9673f53ecf50 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Mon, 28 Sep 2020 00:49:58 +0800 Subject: [PATCH] dhcpd: added dhcp service (ipv4 only) --- feature_linux.go | 2 +- service/dhcpd/dhcpd_linux.go | 157 +++++++++++++++++++++++++++++++++++ service/dhcpd/pool.go | 67 +++++++++++++++ systemd/glider@.service | 5 +- 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 service/dhcpd/dhcpd_linux.go create mode 100644 service/dhcpd/pool.go diff --git a/feature_linux.go b/feature_linux.go index 8c37b96..1bfb905 100644 --- a/feature_linux.go +++ b/feature_linux.go @@ -2,7 +2,7 @@ package main import ( // comment out the services you don't need to make the compiled binary smaller. - // _ "github.com/nadoo/glider/service/xxx" + _ "github.com/nadoo/glider/service/dhcpd" // comment out the protocols you don't need to make the compiled binary smaller. _ "github.com/nadoo/glider/proxy/redir" diff --git a/service/dhcpd/dhcpd_linux.go b/service/dhcpd/dhcpd_linux.go new file mode 100644 index 0000000..2794a3d --- /dev/null +++ b/service/dhcpd/dhcpd_linux.go @@ -0,0 +1,157 @@ +package dhcpd + +import ( + "context" + "net" + "time" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/dhcpv4/nclient4" + "github.com/insomniacslk/dhcp/dhcpv4/server4" + + "github.com/nadoo/glider/common/log" + "github.com/nadoo/glider/service" +) + +var leaseTime = time.Hour * 12 + +func init() { + service.Register("dhcpd", &dpcpd{}) +} + +type dpcpd struct{} + +func (*dpcpd) Run(args ...string) { + if len(args) < 3 { + log.F("[dhcpd] not enough parameters, exiting") + return + } + + iface, ipStart, ipEnd := args[0], args[1], args[2] + + if detectServer(iface) { + log.F("[dhcpd] found existing dhcp server on interface %s, service exiting", iface) + return + } + + pool, err := NewPool(leaseTime, net.ParseIP(ipStart), net.ParseIP(ipEnd)) + if err != nil { + log.F("[dhcpd] error in pool init: %s", err) + return + } + + ip, mask := ifaceIPMask4(iface) + if ip == nil || mask == nil { + log.F("[dhcpd] can not get ip and mask of interface: %s", iface) + return + } + + server, err := server4.NewServer( + iface, &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}, handleDHCP(ip, mask, pool)) + if err != nil { + log.F("[dhcpd] error in server new: %s", err) + return + } + + log.F("[dhcpd] listening on interface %s(%s/%d.%d.%d.%d)", + iface, ip, mask[0], mask[1], mask[2], mask[3]) + + server.Serve() +} + +func handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4.Handler { + return func(conn net.PacketConn, peer net.Addr, m *dhcpv4.DHCPv4) { + log.F("[dpcpd] received request from client %v", m.ClientHWAddr) + + var replyType dhcpv4.MessageType + switch mt := m.MessageType(); mt { + case dhcpv4.MessageTypeDiscover: + replyType = dhcpv4.MessageTypeOffer + case dhcpv4.MessageTypeRequest: + replyType = dhcpv4.MessageTypeAck + default: + log.F("[dpcpd] can't handle type %v", mt) + return + } + + replyIp, err := pool.AssignIP(m.ClientHWAddr) + if err != nil { + log.F("[dpcpd] can not assign IP error %s", err) + return + } + + reply, err := dhcpv4.NewReplyFromRequest(m, + dhcpv4.WithMessageType(replyType), + dhcpv4.WithServerIP(serverIP), + dhcpv4.WithRouter(serverIP), + dhcpv4.WithNetmask(mask), + dhcpv4.WithYourIP(replyIp), + // RFC 2131, Section 4.3.1. Server Identifier: MUST + dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)), + // RFC 2131, Section 4.3.1. IP lease time: MUST + dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(leaseTime)), + dhcpv4.WithRouter(serverIP), + dhcpv4.WithDNS(serverIP), + ) + + if val := m.Options.Get(dhcpv4.OptionClientIdentifier); len(val) > 0 { + reply.UpdateOption(dhcpv4.OptGeneric(dhcpv4.OptionClientIdentifier, val)) + } + + if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil { + log.F("[dpcpd] could not write %v: %s", reply, err) + return + } + + log.F("[dpcpd] lease %v to client %v", replyIp, reply.ClientHWAddr) + } +} + +func detectServer(iface string) (exists bool) { + client, err := nclient4.New(iface) + if err != nil { + log.F("[dhcpd] failed in dhcp client creation: %s", err) + return + } + defer client.Close() + + ctx, _ := context.WithTimeout(context.Background(), 3*time.Second) + _, err = client.Request(ctx) + if err != nil { + return + } + + return true +} + +func ifaceIPMask4(iface string) (net.IP, net.IPMask) { + intf, err := net.InterfaceByName(iface) + if err != nil { + return nil, nil + } + + addrs, err := intf.Addrs() + if err != nil { + return nil, nil + } + + for _, addr := range addrs { + var ip net.IP + var mask net.IPMask + + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + mask = v.Mask + case *net.IPAddr: + ip = v.IP + mask = ip.DefaultMask() + } + + if ip4 := ip.To4(); ip4 != nil { + return ip4, mask + } + } + + return nil, nil +} diff --git a/service/dhcpd/pool.go b/service/dhcpd/pool.go new file mode 100644 index 0000000..6ff2fc0 --- /dev/null +++ b/service/dhcpd/pool.go @@ -0,0 +1,67 @@ +package dhcpd + +import ( + "bytes" + "errors" + "net" + "time" +) + +type Pool struct { + lease time.Duration + items []*item +} + +func NewPool(lease time.Duration, ipStart, ipEnd net.IP) (*Pool, error) { + items := make([]*item, 0) + var currentIp = ipStart.To4() + for bytes.Compare(currentIp, ipEnd.To4()) <= 0 { + ip := make([]byte, 4) + copy(ip, currentIp) + i := &item{ + lease: lease, + ip: ip, + } + items = append(items, i) + currentIp[3]++ + } + return &Pool{lease: lease, items: items}, nil +} + +func (p *Pool) AssignIP(mac net.HardwareAddr) (net.IP, error) { + var ip net.IP + for _, item := range p.items { + if mac.String() == item.hardwareAddr.String() { + return item.ip, nil + } + } + for _, item := range p.items { + if ip = item.take(mac); ip != nil { + return ip, nil + } + } + return nil, errors.New("no more ip can be assigned") +} + +type item struct { + ip net.IP + lease time.Duration + taken bool + hardwareAddr net.HardwareAddr +} + +func (i *item) take(addr net.HardwareAddr) net.IP { + if i.taken { + return nil + } else { + i.taken = true + go func() { + timer := time.NewTimer(i.lease) + <-timer.C + i.hardwareAddr = nil + i.taken = false + }() + i.hardwareAddr = addr + return i.ip + } +} diff --git a/systemd/glider@.service b/systemd/glider@.service index ae6ef46..46b6d1b 100644 --- a/systemd/glider@.service +++ b/systemd/glider@.service @@ -14,8 +14,9 @@ ExecStart=/usr/bin/glider -config /etc/glider/%i.conf # work with systemd v229 or later, so glider can listen on port below 1024 with none-root user # CAP_NET_ADMIN: ipset # CAP_NET_BIND_SERVICE: bind ports under 1024 -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +# CAP_NET_RAW: bind raw socket and broadcasting (used by dhcpd) +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW NoNewPrivileges=true [Install]