From cbc51b8842004bbc3da8371bd0f32c65c84cf2ce Mon Sep 17 00:00:00 2001 From: Michael Cambria Date: Mon, 15 Oct 2018 10:41:02 -0400 Subject: [PATCH] Change dhcp plugin to send ClientID allowing container to have multiple CNI interfaces using dhcp ipam. Vendor latest dhcp4server, dhcp4client, dhcp4 Added additional tests for new functionality in dhcp2_test.go Wrap d2g dhcp4client calls with our own which add clientID to packet. --- plugins/ipam/dhcp/client.go | 243 +++++++++++++++ plugins/ipam/dhcp/daemon.go | 5 +- plugins/ipam/dhcp/dhcp2_test.go | 294 ++++++++++++++++++ plugins/ipam/dhcp/dhcp_test.go | 282 ++++++++++++++++- plugins/ipam/dhcp/lease.go | 23 +- vendor/github.com/d2g/dhcp4/option.go | 2 +- vendor/github.com/d2g/dhcp4/packet.go | 5 +- vendor/github.com/d2g/dhcp4client/client.go | 178 +++++++---- .../github.com/d2g/dhcp4client/generatexid.go | 18 ++ vendor/github.com/d2g/dhcp4client/init.go | 10 - .../d2g/dhcp4client/pktsock_linux.go | 2 +- .../d2g/dhcp4server/leasepool/lease.go | 59 ++-- .../d2g/dhcp4server/leasepool/lease_test.go | 1 + .../d2g/dhcp4server/leasepool/leasepool.go | 4 +- .../leasepool/memorypool/memorypool.go | 17 +- vendor/github.com/d2g/dhcp4server/server.go | 26 +- .../github.com/d2g/dhcp4server/server_test.go | 64 ++++ 17 files changed, 1102 insertions(+), 131 deletions(-) create mode 100644 plugins/ipam/dhcp/client.go create mode 100644 plugins/ipam/dhcp/dhcp2_test.go create mode 100644 vendor/github.com/d2g/dhcp4client/generatexid.go delete mode 100644 vendor/github.com/d2g/dhcp4client/init.go diff --git a/plugins/ipam/dhcp/client.go b/plugins/ipam/dhcp/client.go new file mode 100644 index 000000000..216fa6c17 --- /dev/null +++ b/plugins/ipam/dhcp/client.go @@ -0,0 +1,243 @@ +package main + +import ( + "github.com/d2g/dhcp4" + "github.com/d2g/dhcp4client" +) + +const ( + MaxDHCPLen = 576 +) + +//Send the Discovery Packet to the Broadcast Channel +func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) { + discoveryPacket := c.DiscoverPacket() + + for opt, data := range options { + discoveryPacket.AddOption(opt, data) + } + + discoveryPacket.PadToMinSize() + return discoveryPacket, c.SendPacket(discoveryPacket) +} + +//Send Request Based On the offer Received. +func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) { + requestPacket := c.RequestPacket(offerPacket) + + for opt, data := range options { + requestPacket.AddOption(opt, data) + } + + requestPacket.PadToMinSize() + + return requestPacket, c.SendPacket(requestPacket) +} + +//Send Decline to the received acknowledgement. +func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) { + declinePacket := c.DeclinePacket(acknowledgementPacket) + + for opt, data := range options { + declinePacket.AddOption(opt, data) + } + + declinePacket.PadToMinSize() + + return declinePacket, c.SendPacket(declinePacket) +} + +/* +//Create Discover Packet +func DhcpDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) dhcp4.Packet { + messageid := make([]byte, 4) + c.generateXID(messageid) + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(c.hardwareAddr) + packet.SetXId(messageid) + packet.SetBroadcast(c.broadcast) + + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Discover)}) + for opt, data := range options { + packet.AddOption(opt, data) + } + //packet.PadToMinSize() + return packet +} +*/ + +/* +//Create Request Packet +func DhcpRequestPacket(c *dhcp4client.Client, offerPacket *dhcp4.Packet, options dhcp4.Options) dhcp4.Packet { + offerOptions := offerPacket.ParseOptions() + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(c.hardwareAddr) + + packet.SetXId(offerPacket.XId()) + packet.SetCIAddr(offerPacket.CIAddr()) + packet.SetSIAddr(offerPacket.SIAddr()) + + packet.SetBroadcast(c.broadcast) + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)}) + packet.AddOption(dhcp4.OptionRequestedIPAddress, (offerPacket.YIAddr()).To4()) + packet.AddOption(dhcp4.OptionServerIdentifier, offerOptions[dhcp4.OptionServerIdentifier]) + + for opt, data := range options { + packet.AddOption(opt, data) + } + + return packet +} +*/ + +/* +//Create Request Packet For a Renew +func DhcpRenewalRequestPacket(c *dhcp4client.Client, acknowledgement *dhcp4.Packet, options dhcp4.Options) dhcp4.Packet { + messageid := make([]byte, 4) + c.generateXID(messageid) + + acknowledgementOptions := acknowledgement.ParseOptions() + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(acknowledgement.CHAddr()) + + packet.SetXId(messageid) + packet.SetCIAddr(acknowledgement.YIAddr()) + packet.SetSIAddr(acknowledgement.SIAddr()) + + packet.SetBroadcast(c.broadcast) + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)}) + packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4()) + packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) + + for opt, data := range options { + packet.AddOption(opt, data) + } + + return packet +} +*/ + +/* +//Create Release Packet For a Release +func DhcpReleasePacket(c *dhcp4client.Client, acknowledgement *dhcp4.Packet, options dhcp4.Options) dhcp4.Packet { + messageid := make([]byte, 4) + c.generateXID(messageid) + + acknowledgementOptions := acknowledgement.ParseOptions() + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(acknowledgement.CHAddr()) + + packet.SetXId(messageid) + packet.SetCIAddr(acknowledgement.YIAddr()) + + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Release)}) + packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) + + for opt, data := range options { + packet.AddOption(opt, data) + } + + return packet +} +*/ + +/* +//Create Decline Packet +func DhcpDeclinePacket(c *dhcp4client.Client, acknowledgement *dhcp4.Packet, options dhcp4.Options) dhcp4.Packet { + messageid := make([]byte, 4) + c.generateXID(messageid) + + acknowledgementOptions := acknowledgement.ParseOptions() + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(acknowledgement.CHAddr()) + packet.SetXId(messageid) + + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Decline)}) + packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4()) + packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) + + for opt, data := range options { + packet.AddOption(opt, data) + } + + return packet +} +*/ + +//Lets do a Full DHCP Request. +func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) { + discoveryPacket, err := DhcpSendDiscoverPacket(c, options) + if err != nil { + return false, discoveryPacket, err + } + + offerPacket, err := c.GetOffer(&discoveryPacket) + if err != nil { + return false, offerPacket, err + } + + requestPacket, err := DhcpSendRequest(c, options, &offerPacket) + if err != nil { + return false, requestPacket, err + } + + acknowledgement, err := c.GetAcknowledgement(&requestPacket) + if err != nil { + return false, acknowledgement, err + } + + acknowledgementOptions := acknowledgement.ParseOptions() + if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { + return false, acknowledgement, nil + } + + return true, acknowledgement, nil +} + +//Renew a lease backed on the Acknowledgement Packet. +//Returns Sucessfull, The AcknoledgementPacket, Any Errors +func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) { + renewRequest := c.RenewalRequestPacket(&acknowledgement) + + for opt, data := range options { + renewRequest.AddOption(opt, data) + } + + renewRequest.PadToMinSize() + + err := c.SendPacket(renewRequest) + if err != nil { + return false, renewRequest, err + } + + newAcknowledgement, err := c.GetAcknowledgement(&renewRequest) + if err != nil { + return false, newAcknowledgement, err + } + + newAcknowledgementOptions := newAcknowledgement.ParseOptions() + if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { + return false, newAcknowledgement, nil + } + + return true, newAcknowledgement, nil +} + +//Release a lease backed on the Acknowledgement Packet. +//Returns Any Errors +func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error { + release := c.ReleasePacket(&acknowledgement) + + for opt, data := range options { + release.AddOption(opt, data) + } + + release.PadToMinSize() + + return c.SendPacket(release) +} diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index b8b287b36..4d1c1a6f3 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -51,9 +51,7 @@ func newDHCP() *DHCP { } func generateClientID(containerID string, netName string, ifName string) string { - clientID := containerID + "/" + netName + "/" + ifName - - return clientID + return containerID + "/" + netName + "/" + ifName } // Allocate acquires an IP from a DHCP server for a specified container. @@ -118,7 +116,6 @@ func (d *DHCP) getLease(clientID string) *DHCPLease { return l } -//func (d *DHCP) setLease(contID, netName string, ifName string, l *DHCPLease) { func (d *DHCP) setLease(clientID string, l *DHCPLease) { d.mux.Lock() defer d.mux.Unlock() diff --git a/plugins/ipam/dhcp/dhcp2_test.go b/plugins/ipam/dhcp/dhcp2_test.go new file mode 100644 index 000000000..ae3450cf3 --- /dev/null +++ b/plugins/ipam/dhcp/dhcp2_test.go @@ -0,0 +1,294 @@ +// Copyright 2015-2018 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "sync" + "time" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/containernetworking/plugins/pkg/testutils" + + "github.com/vishvananda/netlink" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("DHCP Multiple Lease Operations", func() { + var originalNS, targetNS ns.NetNS + var dhcpServerStopCh chan bool + var dhcpServerDone *sync.WaitGroup + var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var err error + + var br *netlink.Bridge + + BeforeEach(func() { + dhcpServerStopCh = make(chan bool) + + tmpDir, err = getTmpDir() + Expect(err).NotTo(HaveOccurred()) + socketPath = filepath.Join(tmpDir, "dhcp.sock") + + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + serverIP := net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + + // Use (original) NS + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // Create bridge in the "host" (original) NS + //linkAttrs := netlink.LinkAttrs{Name: hostBridgeName} + //br = &netlink.Bridge{linkAttrs} + br = &netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: hostBridgeName, + }, + } + + err = netlink.LinkAdd(br) + Expect(err).NotTo(HaveOccurred()) + + address := &netlink.Addr{IPNet: &net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + }} + err = netlink.AddrAdd(br, address) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(br) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.RouteAdd(&netlink.Route{ + LinkIndex: br.Attrs().Index, + Scope: netlink.SCOPE_UNIVERSE, + Dst: &net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create veth pair eth0 + vethLinkAttrs := netlink.NewLinkAttrs() + vethLinkAttrs.Name = hostVethName0 + + veth := &netlink.Veth{ + LinkAttrs: vethLinkAttrs, + PeerName: contVethName0, + } + err = netlink.LinkAdd(veth) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err := netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink, err := netlink.LinkByName(hostVethName0) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetNsFd(cont, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + // Create veth path - eth1 + vethLinkAttrs1 := netlink.NewLinkAttrs() + vethLinkAttrs1.Name = hostVethName1 + + veth1 := &netlink.Veth{ + LinkAttrs: vethLinkAttrs1, + PeerName: contVethName1, + } + err = netlink.LinkAdd(veth1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth1) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err = netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink1, err := netlink.LinkByName(hostVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink1, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont1, err := netlink.LinkByName(contVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetNsFd(cont1, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Move the container side to the container's NS + err = targetNS.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // Start the DHCP server + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 2, dhcpServerStopCh) + Expect(err).NotTo(HaveOccurred()) + + // Start the DHCP client daemon + dhcpPluginPath, err := exec.LookPath("dhcp") + Expect(err).NotTo(HaveOccurred()) + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) + err = clientCmd.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(clientCmd.Process).NotTo(BeNil()) + + // Wait up to 15 seconds for the client socket + Eventually(func() bool { + _, err := os.Stat(socketPath) + return err == nil + }, time.Second*15, time.Second/4).Should(BeTrue()) + }) + + AfterEach(func() { + dhcpServerStopCh <- true + dhcpServerDone.Wait() + clientCmd.Process.Kill() + clientCmd.Wait() + + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + defer os.RemoveAll(tmpDir) + }) + + It("configures multiple links with multiple ADD/DEL", func() { + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "bridge", + "bridge": "%s", + "ipam": { + "type": "dhcp", + "daemonSocketPath": "%s" + } + }`, hostBridgeName, socketPath) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + var addResult *current.Result + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + //var addResult *current.Result + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.6/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/plugins/ipam/dhcp/dhcp_test.go b/plugins/ipam/dhcp/dhcp_test.go index b865c18fa..c79ad0f58 100644 --- a/plugins/ipam/dhcp/dhcp_test.go +++ b/plugins/ipam/dhcp/dhcp_test.go @@ -49,12 +49,20 @@ func getTmpDir() (string, error) { return tmpDir, err } -func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan bool) (*sync.WaitGroup, error) { +func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) { // Add the expected IP to the pool lp := memorypool.MemoryPool{} - err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0)}) - if err != nil { - return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err) + + Expect(numLeases).To(BeNumerically(">", 0)) + // Currently tests only need at most 2 + Expect(numLeases).To(BeNumerically("<=", 2)) + + // tests expect first lease to be at address 192.168.1.5 + for i := 5; i < numLeases+5; i++ { + err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)}) + if err != nil { + return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err) + } } dhcpServer, err := dhcp4server.New( @@ -193,7 +201,7 @@ var _ = Describe("DHCP Operations", func() { }) // Start the DHCP server - dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, dhcpServerStopCh) + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh) Expect(err).NotTo(HaveOccurred()) // Start the DHCP client daemon @@ -331,3 +339,267 @@ var _ = Describe("DHCP Operations", func() { Expect(err).NotTo(HaveOccurred()) }) }) + +const ( + hostBridgeName string = "dhcpbr0" + hostVethName0 string = "br-eth0" + contVethName0 string = "eth0" + hostVethName1 string = "br-eth1" + contVethName1 string = "eth1" +) + +var _ = Describe("DHCP Lease Unavailable Operations", func() { + var originalNS, targetNS ns.NetNS + var dhcpServerStopCh chan bool + var dhcpServerDone *sync.WaitGroup + var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var err error + + var br *netlink.Bridge + + BeforeEach(func() { + dhcpServerStopCh = make(chan bool) + + tmpDir, err = getTmpDir() + Expect(err).NotTo(HaveOccurred()) + socketPath = filepath.Join(tmpDir, "dhcp.sock") + + // Create a new NetNS so we don't modify the host + var err error + originalNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + targetNS, err = testutils.NewNS() + Expect(err).NotTo(HaveOccurred()) + + serverIP := net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + } + + // Use (original) NS + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + // Create bridge in the "host" (original) NS + //linkAttrs := netlink.LinkAttrs{Name: hostBridgeName} + //br = &netlink.Bridge{linkAttrs} + br = &netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: hostBridgeName, + }, + } + + err = netlink.LinkAdd(br) + Expect(err).NotTo(HaveOccurred()) + + //err = netlink.AddrAdd(br, &netlink.Addr{IPNet: &serverIP}) + address := &netlink.Addr{IPNet: &net.IPNet{ + IP: net.IPv4(192, 168, 1, 1), + Mask: net.IPv4Mask(255, 255, 255, 0), + }} + err = netlink.AddrAdd(br, address) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(br) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.RouteAdd(&netlink.Route{ + LinkIndex: br.Attrs().Index, + Scope: netlink.SCOPE_UNIVERSE, + Dst: &net.IPNet{ + IP: net.IPv4(0, 0, 0, 0), + Mask: net.IPv4Mask(0, 0, 0, 0), + }, + }) + Expect(err).NotTo(HaveOccurred()) + + // Create veth pair eth0 + vethLinkAttrs := netlink.NewLinkAttrs() + vethLinkAttrs.Name = hostVethName0 + + veth := &netlink.Veth{ + LinkAttrs: vethLinkAttrs, + PeerName: contVethName0, + } + err = netlink.LinkAdd(veth) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err := netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink, err := netlink.LinkByName(hostVethName0) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetNsFd(cont, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + // Create veth path - eth1 + vethLinkAttrs1 := netlink.NewLinkAttrs() + vethLinkAttrs1.Name = hostVethName1 + + veth1 := &netlink.Veth{ + LinkAttrs: vethLinkAttrs1, + PeerName: contVethName1, + } + err = netlink.LinkAdd(veth1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetUp(veth1) + Expect(err).NotTo(HaveOccurred()) + + bridgeLink, err = netlink.LinkByName(hostBridgeName) + Expect(err).NotTo(HaveOccurred()) + + hostVethLink1, err := netlink.LinkByName(hostVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetMaster(hostVethLink1, bridgeLink.(*netlink.Bridge)) + Expect(err).NotTo(HaveOccurred()) + + cont1, err := netlink.LinkByName(contVethName1) + Expect(err).NotTo(HaveOccurred()) + + err = netlink.LinkSetNsFd(cont1, int(targetNS.Fd())) + Expect(err).NotTo(HaveOccurred()) + + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + // Move the container side to the container's NS + err = targetNS.Do(func(_ ns.NetNS) error { + defer GinkgoRecover() + + link, err := netlink.LinkByName(contVethName0) + Expect(err).NotTo(HaveOccurred()) + err = netlink.LinkSetUp(link) + Expect(err).NotTo(HaveOccurred()) + return nil + }) + + // Start the DHCP server + dhcpServerDone, err = dhcpServerStart(originalNS, net.IPv4(192, 168, 1, 5), serverIP.IP, 1, dhcpServerStopCh) + Expect(err).NotTo(HaveOccurred()) + + // Start the DHCP client daemon + dhcpPluginPath, err := exec.LookPath("dhcp") + Expect(err).NotTo(HaveOccurred()) + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) + err = clientCmd.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(clientCmd.Process).NotTo(BeNil()) + + // Wait up to 15 seconds for the client socket + Eventually(func() bool { + _, err := os.Stat(socketPath) + return err == nil + }, time.Second*15, time.Second/4).Should(BeTrue()) + }) + + AfterEach(func() { + dhcpServerStopCh <- true + dhcpServerDone.Wait() + clientCmd.Process.Kill() + clientCmd.Wait() + + Expect(originalNS.Close()).To(Succeed()) + Expect(targetNS.Close()).To(Succeed()) + defer os.RemoveAll(tmpDir) + }) + + It("Configures multiple links with multiple ADD with second lease unavailable", func() { + conf := fmt.Sprintf(`{ + "cniVersion": "0.3.1", + "name": "mynet", + "type": "bridge", + "bridge": "%s", + "ipam": { + "type": "dhcp", + "daemonSocketPath": "%s" + } + }`, hostBridgeName, socketPath) + + args := &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + var addResult *current.Result + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + r, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + addResult, err = current.GetResult(r) + Expect(err).NotTo(HaveOccurred()) + Expect(len(addResult.IPs)).To(Equal(1)) + Expect(addResult.IPs[0].Address.String()).To(Equal("192.168.1.5/24")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + _, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).To(HaveOccurred()) + println(err.Error()) + Expect(err.Error()).To(Equal("error calling DHCP.Allocate: no more tries")) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName1, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + + args = &skel.CmdArgs{ + ContainerID: "dummy", + Netns: targetNS.Path(), + IfName: contVethName0, + StdinData: []byte(conf), + } + + err = originalNS.Do(func(ns.NetNS) error { + return testutils.CmdDelWithArgs(args, func() error { + return cmdDel(args) + }) + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/plugins/ipam/dhcp/lease.go b/plugins/ipam/dhcp/lease.go index c4c1b1cdf..f34195fd2 100644 --- a/plugins/ipam/dhcp/lease.go +++ b/plugins/ipam/dhcp/lease.go @@ -115,7 +115,7 @@ func (l *DHCPLease) Stop() { } func (l *DHCPLease) acquire() error { - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } @@ -128,8 +128,11 @@ func (l *DHCPLease) acquire() error { } } + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { - ok, ack, err := c.Request() + ok, ack, err := DhcpRequest(c, opts) switch { case err != nil: return nil, err @@ -238,14 +241,17 @@ func (l *DHCPLease) downIface() { } func (l *DHCPLease) renew() error { - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } defer c.Close() + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + pkt, err := backoffRetry(func() (*dhcp4.Packet, error) { - ok, ack, err := c.Renew(*l.ack) + ok, ack, err := DhcpRenew(c, *l.ack, opts) switch { case err != nil: return nil, err @@ -266,13 +272,16 @@ func (l *DHCPLease) renew() error { func (l *DHCPLease) release() error { log.Printf("%v: releasing lease", l.clientID) - c, err := newDHCPClient(l.link) + c, err := newDHCPClient(l.link, l.clientID) if err != nil { return err } defer c.Close() - if err = c.Release(*l.ack); err != nil { + opts := make(dhcp4.Options) + opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID) + + if err = DhcpRelease(c, *l.ack, opts); err != nil { return fmt.Errorf("failed to send DHCPRELEASE") } @@ -344,7 +353,7 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) { return nil, errNoMoreTries } -func newDHCPClient(link netlink.Link) (*dhcp4client.Client, error) { +func newDHCPClient(link netlink.Link, clientID string) (*dhcp4client.Client, error) { pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index) if err != nil { return nil, err diff --git a/vendor/github.com/d2g/dhcp4/option.go b/vendor/github.com/d2g/dhcp4/option.go index f3239e198..fbf86e65c 100644 --- a/vendor/github.com/d2g/dhcp4/option.go +++ b/vendor/github.com/d2g/dhcp4/option.go @@ -11,7 +11,7 @@ type Option struct { type Options map[OptionCode][]byte // SelectOrderOrAll has same functionality as SelectOrder, except if the order -// param is nil, whereby all options are added (in arbitary order). +// param is nil, whereby all options are added (in arbitrary order). func (o Options) SelectOrderOrAll(order []byte) []Option { if order == nil { opts := make([]Option, 0, len(o)) diff --git a/vendor/github.com/d2g/dhcp4/packet.go b/vendor/github.com/d2g/dhcp4/packet.go index 5e547c86f..25d69fb99 100644 --- a/vendor/github.com/d2g/dhcp4/packet.go +++ b/vendor/github.com/d2g/dhcp4/packet.go @@ -127,10 +127,11 @@ func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDurat p.SetYIAddr(yIAddr) p.SetGIAddr(req.GIAddr()) p.SetCHAddr(req.CHAddr()) - p.SetSecs(req.Secs()) p.AddOption(OptionDHCPMessageType, []byte{byte(mt)}) p.AddOption(OptionServerIdentifier, []byte(serverId)) - p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration)) + if leaseDuration > 0 { + p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration)) + } for _, o := range options { p.AddOption(o.Code, o.Value) } diff --git a/vendor/github.com/d2g/dhcp4client/client.go b/vendor/github.com/d2g/dhcp4client/client.go index ab852c650..1d43e3076 100644 --- a/vendor/github.com/d2g/dhcp4client/client.go +++ b/vendor/github.com/d2g/dhcp4client/client.go @@ -2,8 +2,12 @@ package dhcp4client import ( "bytes" - "crypto/rand" + "fmt" + "hash/fnv" + "math/rand" "net" + "sync" + "syscall" "time" "github.com/d2g/dhcp4" @@ -18,13 +22,12 @@ type Client struct { ignoreServers []net.IP //List of Servers to Ignore requests from. timeout time.Duration //Time before we timeout. broadcast bool //Set the Bcast flag in BOOTP Flags - connection connection //The Connection Method to use + connection ConnectionInt //The Connection Method to use + generateXID func([]byte) //Function Used to Generate a XID } -/* - * Abstracts the type of underlying socket used - */ -type connection interface { +//Abstracts the type of underlying socket used +type ConnectionInt interface { Close() error Write(packet []byte) error ReadFrom() ([]byte, net.IP, error) @@ -42,6 +45,26 @@ func New(options ...func(*Client) error) (*Client, error) { return nil, err } + if c.generateXID == nil { + // https://tools.ietf.org/html/rfc2131#section-4.1 explains: + // + // A DHCP client MUST choose 'xid's in such a way as to minimize the chance + // of using an 'xid' identical to one used by another client. + // + // Hence, seed a random number generator with the current time and hardware + // address. + h := fnv.New64() + h.Write(c.hardwareAddr) + seed := int64(h.Sum64()) + time.Now().Unix() + rnd := rand.New(rand.NewSource(seed)) + var rndMu sync.Mutex + c.generateXID = func(b []byte) { + rndMu.Lock() + defer rndMu.Unlock() + rnd.Read(b) + } + } + //if connection hasn't been set as an option create the default. if c.connection == nil { conn, err := NewInetSock() @@ -91,16 +114,21 @@ func Broadcast(b bool) func(*Client) error { } } -func Connection(conn connection) func(*Client) error { +func Connection(conn ConnectionInt) func(*Client) error { return func(c *Client) error { c.connection = conn return nil } } -/* - * Close Connections - */ +func GenerateXID(g func([]byte)) func(*Client) error { + return func(c *Client) error { + c.generateXID = g + return nil + } +} + +//Close Connections func (c *Client) Close() error { if c.connection != nil { return c.connection.Close() @@ -108,9 +136,7 @@ func (c *Client) Close() error { return nil } -/* - * Send the Discovery Packet to the Broadcast Channel - */ +//Send the Discovery Packet to the Broadcast Channel func (c *Client) SendDiscoverPacket() (dhcp4.Packet, error) { discoveryPacket := c.DiscoverPacket() discoveryPacket.PadToMinSize() @@ -118,15 +144,32 @@ func (c *Client) SendDiscoverPacket() (dhcp4.Packet, error) { return discoveryPacket, c.SendPacket(discoveryPacket) } -/* - * Retreive Offer... - * Wait for the offer for a specific Discovery Packet. - */ +// TimeoutError records a timeout when waiting for a DHCP packet. +type TimeoutError struct { + Timeout time.Duration +} + +func (te *TimeoutError) Error() string { + return fmt.Sprintf("no DHCP packet received within %v", te.Timeout) +} + +//Retreive Offer... +//Wait for the offer for a specific Discovery Packet. func (c *Client) GetOffer(discoverPacket *dhcp4.Packet) (dhcp4.Packet, error) { + start := time.Now() + for { - c.connection.SetReadTimeout(c.timeout) + timeout := c.timeout - time.Since(start) + if timeout < 0 { + return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout} + } + + c.connection.SetReadTimeout(timeout) readBuffer, source, err := c.connection.ReadFrom() if err != nil { + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN { + return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout} + } return dhcp4.Packet{}, err } @@ -153,9 +196,7 @@ func (c *Client) GetOffer(discoverPacket *dhcp4.Packet) (dhcp4.Packet, error) { } -/* - * Send Request Based On the offer Received. - */ +//Send Request Based On the offer Received. func (c *Client) SendRequest(offerPacket *dhcp4.Packet) (dhcp4.Packet, error) { requestPacket := c.RequestPacket(offerPacket) requestPacket.PadToMinSize() @@ -163,15 +204,23 @@ func (c *Client) SendRequest(offerPacket *dhcp4.Packet) (dhcp4.Packet, error) { return requestPacket, c.SendPacket(requestPacket) } -/* - * Retreive Acknowledgement - * Wait for the offer for a specific Request Packet. - */ +//Retreive Acknowledgement +//Wait for the offer for a specific Request Packet. func (c *Client) GetAcknowledgement(requestPacket *dhcp4.Packet) (dhcp4.Packet, error) { + start := time.Now() + for { - c.connection.SetReadTimeout(c.timeout) + timeout := c.timeout - time.Since(start) + if timeout < 0 { + return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout} + } + + c.connection.SetReadTimeout(timeout) readBuffer, source, err := c.connection.ReadFrom() if err != nil { + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN { + return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout} + } return dhcp4.Packet{}, err } @@ -197,21 +246,23 @@ func (c *Client) GetAcknowledgement(requestPacket *dhcp4.Packet) (dhcp4.Packet, } } -/* - * Send a DHCP Packet. - */ +//Send Decline to the received acknowledgement. +func (c *Client) SendDecline(acknowledgementPacket *dhcp4.Packet) (dhcp4.Packet, error) { + declinePacket := c.DeclinePacket(acknowledgementPacket) + declinePacket.PadToMinSize() + + return declinePacket, c.SendPacket(declinePacket) +} + +//Send a DHCP Packet. func (c *Client) SendPacket(packet dhcp4.Packet) error { return c.connection.Write(packet) } -/* - * Create Discover Packet - */ +//Create Discover Packet func (c *Client) DiscoverPacket() dhcp4.Packet { messageid := make([]byte, 4) - if _, err := rand.Read(messageid); err != nil { - panic(err) - } + c.generateXID(messageid) packet := dhcp4.NewPacket(dhcp4.BootRequest) packet.SetCHAddr(c.hardwareAddr) @@ -223,9 +274,7 @@ func (c *Client) DiscoverPacket() dhcp4.Packet { return packet } -/* - * Create Request Packet - */ +//Create Request Packet func (c *Client) RequestPacket(offerPacket *dhcp4.Packet) dhcp4.Packet { offerOptions := offerPacket.ParseOptions() @@ -241,18 +290,13 @@ func (c *Client) RequestPacket(offerPacket *dhcp4.Packet) dhcp4.Packet { packet.AddOption(dhcp4.OptionRequestedIPAddress, (offerPacket.YIAddr()).To4()) packet.AddOption(dhcp4.OptionServerIdentifier, offerOptions[dhcp4.OptionServerIdentifier]) - //packet.PadToMinSize() return packet } -/* - * Create Request Packet For a Renew - */ +//Create Request Packet For a Renew func (c *Client) RenewalRequestPacket(acknowledgement *dhcp4.Packet) dhcp4.Packet { messageid := make([]byte, 4) - if _, err := rand.Read(messageid); err != nil { - panic(err) - } + c.generateXID(messageid) acknowledgementOptions := acknowledgement.ParseOptions() @@ -268,18 +312,13 @@ func (c *Client) RenewalRequestPacket(acknowledgement *dhcp4.Packet) dhcp4.Packe packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4()) packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) - //packet.PadToMinSize() return packet } -/* - * Create Release Packet For a Release - */ +//Create Release Packet For a Release func (c *Client) ReleasePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet { messageid := make([]byte, 4) - if _, err := rand.Read(messageid); err != nil { - panic(err) - } + c.generateXID(messageid) acknowledgementOptions := acknowledgement.ParseOptions() @@ -292,13 +331,28 @@ func (c *Client) ReleasePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet { packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Release)}) packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) - //packet.PadToMinSize() return packet } -/* - * Lets do a Full DHCP Request. - */ +//Create Decline Packet +func (c *Client) DeclinePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet { + messageid := make([]byte, 4) + c.generateXID(messageid) + + acknowledgementOptions := acknowledgement.ParseOptions() + + packet := dhcp4.NewPacket(dhcp4.BootRequest) + packet.SetCHAddr(acknowledgement.CHAddr()) + packet.SetXId(messageid) + + packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Decline)}) + packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4()) + packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier]) + + return packet +} + +//Lets do a Full DHCP Request. func (c *Client) Request() (bool, dhcp4.Packet, error) { discoveryPacket, err := c.SendDiscoverPacket() if err != nil { @@ -328,10 +382,8 @@ func (c *Client) Request() (bool, dhcp4.Packet, error) { return true, acknowledgement, nil } -/* - * Renew a lease backed on the Acknowledgement Packet. - * Returns Sucessfull, The AcknoledgementPacket, Any Errors - */ +//Renew a lease backed on the Acknowledgement Packet. +//Returns Sucessfull, The AcknoledgementPacket, Any Errors func (c *Client) Renew(acknowledgement dhcp4.Packet) (bool, dhcp4.Packet, error) { renewRequest := c.RenewalRequestPacket(&acknowledgement) renewRequest.PadToMinSize() @@ -354,10 +406,8 @@ func (c *Client) Renew(acknowledgement dhcp4.Packet) (bool, dhcp4.Packet, error) return true, newAcknowledgement, nil } -/* - * Release a lease backed on the Acknowledgement Packet. - * Returns Any Errors - */ +//Release a lease backed on the Acknowledgement Packet. +//Returns Any Errors func (c *Client) Release(acknowledgement dhcp4.Packet) error { release := c.ReleasePacket(&acknowledgement) release.PadToMinSize() diff --git a/vendor/github.com/d2g/dhcp4client/generatexid.go b/vendor/github.com/d2g/dhcp4client/generatexid.go new file mode 100644 index 000000000..6e9ffbcde --- /dev/null +++ b/vendor/github.com/d2g/dhcp4client/generatexid.go @@ -0,0 +1,18 @@ +package dhcp4client + +import ( + cryptorand "crypto/rand" + mathrand "math/rand" +) + +func CryptoGenerateXID(b []byte) { + if _, err := cryptorand.Read(b); err != nil { + panic(err) + } +} + +func MathGenerateXID(b []byte) { + if _, err := mathrand.Read(b); err != nil { + panic(err) + } +} diff --git a/vendor/github.com/d2g/dhcp4client/init.go b/vendor/github.com/d2g/dhcp4client/init.go deleted file mode 100644 index 67ae8a3a1..000000000 --- a/vendor/github.com/d2g/dhcp4client/init.go +++ /dev/null @@ -1,10 +0,0 @@ -package dhcp4client - -import ( - "math/rand" - "time" -) - -func init() { - rand.Seed(time.Now().Unix()) -} diff --git a/vendor/github.com/d2g/dhcp4client/pktsock_linux.go b/vendor/github.com/d2g/dhcp4client/pktsock_linux.go index a21c265fb..99b53521c 100644 --- a/vendor/github.com/d2g/dhcp4client/pktsock_linux.go +++ b/vendor/github.com/d2g/dhcp4client/pktsock_linux.go @@ -1,8 +1,8 @@ package dhcp4client import ( - "crypto/rand" "encoding/binary" + "math/rand" "net" "time" diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/lease.go b/vendor/github.com/d2g/dhcp4server/leasepool/lease.go index 18f9d1289..98306eca1 100644 --- a/vendor/github.com/d2g/dhcp4server/leasepool/lease.go +++ b/vendor/github.com/d2g/dhcp4server/leasepool/lease.go @@ -1,7 +1,10 @@ package leasepool import ( + "bytes" + "encoding/hex" "encoding/json" + "fmt" "net" "time" ) @@ -18,37 +21,35 @@ type Lease struct { IP net.IP //The IP of the Lease Status LeaseStatus //Are Reserved, Active or Free MACAddress net.HardwareAddr //Mac Address of the Device + ClientID []byte //ClientID of the request Hostname string //Hostname From option 12 Expiry time.Time //Expiry Time } -func (this Lease) MarshalJSON() ([]byte, error) { - stringMarshal := struct { - IP string - Status int - MACAddress string - Hostname string - Expiry time.Time - }{ - (this.IP.String()), - int(this.Status), - (this.MACAddress.String()), - this.Hostname, - this.Expiry, - } +//leaseMarshal is a mirror of Lease used for marshalling, since +//net.HardwareAddr has no native marshalling capability. +type leaseMarshal struct { + IP string + Status int + MACAddress string + ClientID string + Hostname string + Expiry time.Time +} - return json.Marshal(stringMarshal) +func (this Lease) MarshalJSON() ([]byte, error) { + return json.Marshal(leaseMarshal{ + IP: this.IP.String(), + Status: int(this.Status), + MACAddress: this.MACAddress.String(), + ClientID: hex.EncodeToString(this.ClientID), + Hostname: this.Hostname, + Expiry: this.Expiry, + }) } func (this *Lease) UnmarshalJSON(data []byte) error { - stringUnMarshal := struct { - IP string - Status int - MACAddress string - Hostname string - Expiry time.Time - }{} - + stringUnMarshal := leaseMarshal{} err := json.Unmarshal(data, &stringUnMarshal) if err != nil { return err @@ -58,12 +59,14 @@ func (this *Lease) UnmarshalJSON(data []byte) error { this.Status = LeaseStatus(stringUnMarshal.Status) if stringUnMarshal.MACAddress != "" { this.MACAddress, err = net.ParseMAC(stringUnMarshal.MACAddress) + if err != nil { + return fmt.Errorf("error parsing MAC address: %v", err) + } } - + this.ClientID, err = hex.DecodeString(stringUnMarshal.ClientID) if err != nil { - return err + return fmt.Errorf("error decoding clientID: %v", err) } - this.Hostname = stringUnMarshal.Hostname this.Expiry = stringUnMarshal.Expiry @@ -83,6 +86,10 @@ func (this Lease) Equal(other Lease) bool { return false } + if !bytes.Equal(this.ClientID, other.ClientID) { + return false + } + if this.Hostname != other.Hostname { return false } diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go b/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go index 2a2e9658f..ac3739500 100644 --- a/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go +++ b/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go @@ -23,6 +23,7 @@ func TestMarshaling(test *testing.T) { if err != nil { test.Error("Error Parsing Mac Address:" + err.Error()) } + startLease.ClientID = []byte("adsfasdfasf") byteStartLease, err := json.Marshal(startLease) if err != nil { diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go b/vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go index e4c9523a0..a620b4b92 100644 --- a/vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go +++ b/vendor/github.com/d2g/dhcp4server/leasepool/leasepool.go @@ -25,8 +25,8 @@ type LeasePool interface { */ GetLease(net.IP) (bool, Lease, error) - //Get the lease already in use by that hardware address. - GetLeaseForHardwareAddress(net.HardwareAddr) (bool, Lease, error) + //Get the lease already in use by that hardware address and/or client identifier. + GetLeaseForClient(net.HardwareAddr, []byte) (bool, Lease, error) /* * -Lease Available diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go index 29ff97c27..7cd8d2d5a 100644 --- a/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go +++ b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go @@ -81,13 +81,23 @@ func (t *MemoryPool) GetLease(leaseIP net.IP) (bool, leasepool.Lease, error) { return false, leasepool.Lease{}, nil } -//Get the lease already in use by that hardware address. -func (t *MemoryPool) GetLeaseForHardwareAddress(macAddress net.HardwareAddr) (bool, leasepool.Lease, error) { +func makeKey(macAddress net.HardwareAddr, clientID []byte) []byte { + key := []byte(macAddress) + if len(clientID) > 0 { + key = append(key, clientID...) + } + return key +} + +//Get the lease already in use by that hardware address and/or client identifier. +func (t *MemoryPool) GetLeaseForClient(macAddress net.HardwareAddr, clientID []byte) (bool, leasepool.Lease, error) { t.poolLock.Lock() defer t.poolLock.Unlock() + needleKey := makeKey(macAddress, clientID) for i := range t.pool { - if bytes.Equal(t.pool[i].MACAddress, macAddress) { + haystackKey := makeKey(t.pool[i].MACAddress, t.pool[i].ClientID) + if bytes.Equal(needleKey, haystackKey) { return true, t.pool[i], nil } } @@ -139,6 +149,7 @@ func (t *MemoryPool) UpdateLease(lease leasepool.Lease) (bool, error) { if t.pool[i].IP.Equal(lease.IP) { t.pool[i].MACAddress = lease.MACAddress + t.pool[i].ClientID = lease.ClientID t.pool[i].Hostname = lease.Hostname t.pool[i].Expiry = lease.Expiry t.pool[i].Status = lease.Status diff --git a/vendor/github.com/d2g/dhcp4server/server.go b/vendor/github.com/d2g/dhcp4server/server.go index 9a4b57ff4..7b1d374d9 100644 --- a/vendor/github.com/d2g/dhcp4server/server.go +++ b/vendor/github.com/d2g/dhcp4server/server.go @@ -172,9 +172,6 @@ func (s *Server) ListenAndServe() error { // return err //} - //Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day. - buffer := make([]byte, 576) - log.Println("Trace: DHCP Server Listening.") for { @@ -183,6 +180,9 @@ func (s *Server) ListenAndServe() error { return nil } + //Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day. + buffer := make([]byte, 576) + //Set Read Deadline s.connection.SetReadDeadline(time.Now().Add(time.Second)) // Read Packet @@ -213,7 +213,6 @@ func (s *Server) ListenAndServe() error { } log.Printf("Debug: Unexpect Error from Connection Read From: %v\n", err) - log.Printf("Debug: err type %T %#v\n", err, err) return err } @@ -281,6 +280,13 @@ func (s *Server) ListenAndServe() error { } } +func getClientID(packetOptions dhcp4.Options) []byte { + if clientID, ok := packetOptions[dhcp4.OptionClientIdentifier]; ok { + return clientID + } + return nil +} + func (s *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) { packetOptions := packet.ParseOptions() @@ -307,6 +313,7 @@ func (s *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) { lease.Status = leasepool.Reserved lease.MACAddress = packet.CHAddr() + lease.ClientID = getClientID(packetOptions) //If the lease expires within the next 5 Mins increase the lease expiary (Giving the Client 5 mins to complete) if lease.Expiry.Before(time.Now().Add(time.Minute * 5)) { @@ -351,6 +358,7 @@ func (s *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) { } else { lease.Status = leasepool.Active lease.MACAddress = packet.CHAddr() + lease.ClientID = getClientID(packetOptions) lease.Expiry = time.Now().Add(s.leaseDuration) @@ -498,6 +506,8 @@ func (s *Server) DeclinePacket(requestPacket dhcp4.Packet) dhcp4.Packet { func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Lease, err error) { packetOptions := packet.ParseOptions() + clientID := getClientID(packetOptions) + //Requested an IP if (len(packetOptions) > 0) && packetOptions[dhcp4.OptionRequestedIPAddress] != nil && @@ -510,11 +520,15 @@ func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Leas } if found { + //If lease is free, return it to client. If it is not + //free match against the MAC address and client + //identifier. if lease.Status == leasepool.Free { //Lease Is Free you Can Have it. return } - if lease.Status != leasepool.Free && bytes.Equal(lease.MACAddress, packet.CHAddr()) { + if bytes.Equal(lease.MACAddress, packet.CHAddr()) && + bytes.Equal(lease.ClientID, clientID) { //Lease isn't free but it's yours return } @@ -522,7 +536,7 @@ func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Leas } //Ok Even if you requested an IP you can't have it. - found, lease, err = s.leasePool.GetLeaseForHardwareAddress(packet.CHAddr()) + found, lease, err = s.leasePool.GetLeaseForClient(packet.CHAddr(), clientID) if found || err != nil { return } diff --git a/vendor/github.com/d2g/dhcp4server/server_test.go b/vendor/github.com/d2g/dhcp4server/server_test.go index 0080b84cb..dc6552c6d 100644 --- a/vendor/github.com/d2g/dhcp4server/server_test.go +++ b/vendor/github.com/d2g/dhcp4server/server_test.go @@ -3,6 +3,7 @@ package dhcp4server_test import ( "bytes" "encoding/binary" + "fmt" "log" "net" "sync" @@ -409,6 +410,69 @@ func BenchmarkServeDHCP(test *testing.B) { } } +/* + * + */ +func TestLeaseByClientID(test *testing.T) { + //Setup the Server + myServer, err := dhcp4server.New( + net.IPv4(127, 0, 0, 1), + getTestLeasePool(), + ) + if err != nil { + test.Error("Error: Can't Configure Server " + err.Error()) + } + + // Setup A Client + // Although We Won't send the packets over the network we'll use the client to create the requests. + c, err := dhcp4client.NewInetSock(dhcp4client.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), dhcp4client.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067})) + if err != nil { + test.Error("Client Conection Generation:" + err.Error()) + } + + client, err := dhcp4client.New(dhcp4client.Connection(c)) + if err != nil { + test.Error("Error: Can't Configure Client " + err.Error()) + } + defer client.Close() + + //Generate Hardware Address; used by both clients + HardwareMACAddress, err := hardwareaddr.GenerateEUI48() + if err != nil { + test.Error("Error: Can't Generate Valid MACAddress" + err.Error()) + } + + for i := 0; i < 2; i++ { + client.SetOption(dhcp4client.HardwareAddr(HardwareMACAddress)) + test.Log("MAC:" + HardwareMACAddress.String()) + + clientID := []byte(fmt.Sprintf("clientid-%d", i)) + test.Log("ClientID:" + string(clientID)) + + discovery := client.DiscoverPacket() + discovery.AddOption(dhcp4.OptionClientIdentifier, clientID) + + //Run the Discovery On the Server + offer, err := myServer.ServeDHCP(discovery) + _, err = myServer.ServeDHCP(discovery) + if err != nil { + test.Error("Discovery Error:" + err.Error()) + } + + request := client.RequestPacket(&offer) + request.AddOption(dhcp4.OptionClientIdentifier, clientID) + acknowledgement, err := myServer.ServeDHCP(request) + if err != nil { + test.Error("Acknowledge Error:" + err.Error()) + } + + test.Logf("Received Lease:%v\n", acknowledgement.YIAddr().String()) + if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).Equal(acknowledgement.YIAddr()) { + test.Error("Expected IP:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).String() + " Received:" + acknowledgement.YIAddr().String()) + } + } +} + func getTestLeasePool() *memorypool.MemoryPool { //Create a Lease Pool We're going to use a memory pool //Remember the memory is cleared on restart so you will reissue the same IP Addresses.