diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 88795fab..ffd50756 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -134,15 +134,15 @@ }, { "ImportPath": "github.com/d2g/dhcp4server", - "Rev": "1b74244053681c90de5cf1af3d6b5c93b74e3abb" + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" }, { "ImportPath": "github.com/d2g/dhcp4server/leasepool", - "Rev": "1b74244053681c90de5cf1af3d6b5c93b74e3abb" + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" }, { "ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool", - "Rev": "1b74244053681c90de5cf1af3d6b5c93b74e3abb" + "Rev": "477b11cea4dcc56af002849238d4f9c1e093c744" }, { "ImportPath": "github.com/j-keck/arping", diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index 2404db8f..fcffd0f8 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -127,7 +127,7 @@ func (d *DHCP) clearLease(contID, netName string) { delete(d.leases, contID+netName) } -func getListener() (net.Listener, error) { +func getListener(socketPath string) (net.Listener, error) { l, err := activation.Listeners() if err != nil { return nil, err @@ -151,7 +151,7 @@ func getListener() (net.Listener, error) { } } -func runDaemon(pidfilePath string, hostPrefix string) error { +func runDaemon(pidfilePath string, hostPrefix string, socketPath string) error { // since other goroutines (on separate threads) will change namespaces, // ensure the RPC server does not get scheduled onto those runtime.LockOSThread() @@ -166,7 +166,7 @@ func runDaemon(pidfilePath string, hostPrefix string) error { } } - l, err := getListener() + l, err := getListener(socketPath) if err != nil { return fmt.Errorf("Error getting listener: %v", err) } diff --git a/plugins/ipam/dhcp/dhcp_test.go b/plugins/ipam/dhcp/dhcp_test.go index 101726e0..b865c18f 100644 --- a/plugins/ipam/dhcp/dhcp_test.go +++ b/plugins/ipam/dhcp/dhcp_test.go @@ -16,9 +16,11 @@ package main import ( "fmt" + "io/ioutil" "net" "os" "os/exec" + "path/filepath" "sync" "time" @@ -38,6 +40,15 @@ import ( . "github.com/onsi/gomega" ) +func getTmpDir() (string, error) { + tmpDir, err := ioutil.TempDir(cniDirPrefix, "dhcp") + if err == nil { + tmpDir = filepath.ToSlash(tmpDir) + } + + return tmpDir, err +} + func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan bool) (*sync.WaitGroup, error) { // Add the expected IP to the pool lp := memorypool.MemoryPool{} @@ -96,17 +107,12 @@ func dhcpServerStart(netns ns.NetNS, leaseIP, serverIP net.IP, stopCh <-chan boo const ( hostVethName string = "dhcp0" contVethName string = "eth0" - pidfilePath string = "/var/run/cni/dhcp-client.pid" + cniDirPrefix string = "/var/run/cni" ) var _ = BeforeSuite(func() { - os.Remove(socketPath) - os.Remove(pidfilePath) -}) - -var _ = AfterSuite(func() { - os.Remove(socketPath) - os.Remove(pidfilePath) + err := os.MkdirAll(cniDirPrefix, 0700) + Expect(err).NotTo(HaveOccurred()) }) var _ = Describe("DHCP Operations", func() { @@ -114,10 +120,17 @@ var _ = Describe("DHCP Operations", func() { var dhcpServerStopCh chan bool var dhcpServerDone *sync.WaitGroup var clientCmd *exec.Cmd + var socketPath string + var tmpDir string + var err error 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() @@ -184,10 +197,9 @@ var _ = Describe("DHCP Operations", func() { Expect(err).NotTo(HaveOccurred()) // Start the DHCP client daemon - os.MkdirAll(pidfilePath, 0755) dhcpPluginPath, err := exec.LookPath("dhcp") Expect(err).NotTo(HaveOccurred()) - clientCmd = exec.Command(dhcpPluginPath, "daemon") + clientCmd = exec.Command(dhcpPluginPath, "daemon", "-socketpath", socketPath) err = clientCmd.Start() Expect(err).NotTo(HaveOccurred()) Expect(clientCmd.Process).NotTo(BeNil()) @@ -207,19 +219,19 @@ var _ = Describe("DHCP Operations", func() { Expect(originalNS.Close()).To(Succeed()) Expect(targetNS.Close()).To(Succeed()) - os.Remove(socketPath) - os.Remove(pidfilePath) + defer os.RemoveAll(tmpDir) }) It("configures and deconfigures a link with ADD/DEL", func() { - conf := `{ + conf := fmt.Sprintf(`{ "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "ipam": { - "type": "dhcp" + "type": "dhcp", + "daemonSocketPath": "%s" } -}` +}`, socketPath) args := &skel.CmdArgs{ ContainerID: "dummy", @@ -254,14 +266,15 @@ var _ = Describe("DHCP Operations", func() { }) It("correctly handles multiple DELs for the same container", func() { - conf := `{ + conf := fmt.Sprintf(`{ "cniVersion": "0.3.1", "name": "mynet", "type": "ipvlan", "ipam": { - "type": "dhcp" + "type": "dhcp", + "daemonSocketPath": "%s" } -}` +}`, socketPath) args := &skel.CmdArgs{ ContainerID: "dummy", diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index f393dd67..70768b43 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -15,6 +15,7 @@ package main import ( + "encoding/json" "flag" "fmt" "log" @@ -28,18 +29,24 @@ import ( "github.com/containernetworking/cni/pkg/version" ) -const socketPath = "/run/cni/dhcp.sock" +const defaultSocketPath = "/run/cni/dhcp.sock" func main() { if len(os.Args) > 1 && os.Args[1] == "daemon" { var pidfilePath string var hostPrefix string + var socketPath string daemonFlags := flag.NewFlagSet("daemon", flag.ExitOnError) daemonFlags.StringVar(&pidfilePath, "pidfile", "", "optional path to write daemon PID to") daemonFlags.StringVar(&hostPrefix, "hostprefix", "", "optional prefix to netns") + daemonFlags.StringVar(&socketPath, "socketpath", "", "optional dhcp server socketpath") daemonFlags.Parse(os.Args[2:]) - if err := runDaemon(pidfilePath, hostPrefix); err != nil { + if socketPath == "" { + socketPath = defaultSocketPath + } + + if err := runDaemon(pidfilePath, hostPrefix, socketPath); err != nil { log.Printf(err.Error()) os.Exit(1) } @@ -78,7 +85,31 @@ func cmdGet(args *skel.CmdArgs) error { return fmt.Errorf("not implemented") } +type SocketPathConf struct { + DaemonSocketPath string `json:"daemonSocketPath,omitempty"` +} + +type TempNetConf struct { + IPAM SocketPathConf `json:"ipam,omitempty"` +} + +func getSocketPath(stdinData []byte) (string, error) { + conf := TempNetConf{} + if err := json.Unmarshal(stdinData, &conf); err != nil { + return "", fmt.Errorf("error parsing socket path conf: %v", err) + } + if conf.IPAM.DaemonSocketPath == "" { + return defaultSocketPath, nil + } + return conf.IPAM.DaemonSocketPath, nil +} + func rpcCall(method string, args *skel.CmdArgs, result interface{}) error { + socketPath, err := getSocketPath(args.StdinData) + if err != nil { + return fmt.Errorf("error obtaining socketPath: %v", err) + } + client, err := rpc.DialHTTP("unix", socketPath) if err != nil { return fmt.Errorf("error dialing DHCP daemon: %v", err) diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go b/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go new file mode 100644 index 00000000..2a2e9658 --- /dev/null +++ b/vendor/github.com/d2g/dhcp4server/leasepool/lease_test.go @@ -0,0 +1,51 @@ +package leasepool + +import ( + "encoding/json" + "net" + "testing" + "time" +) + +/* + * The Leases are Marshalled and Unmarshalled for storage. + * I JSON Marshal these for gvklite + */ +func TestMarshaling(test *testing.T) { + var err error + + startLease := Lease{} + startLease.IP = net.IPv4(192, 168, 0, 1) + startLease.Hostname = "ExampleHostname" + startLease.Status = Active + startLease.Expiry = time.Now() + startLease.MACAddress, err = net.ParseMAC("01:23:45:67:89:ab") + if err != nil { + test.Error("Error Parsing Mac Address:" + err.Error()) + } + + byteStartLease, err := json.Marshal(startLease) + if err != nil { + test.Error("Error Marshaling to JSON:" + err.Error()) + } + + test.Log("StartLease As JSON:" + string(byteStartLease)) + + endLease := Lease{} + err = json.Unmarshal(byteStartLease, &endLease) + if err != nil { + test.Error("Error Unmarshaling to JSON:" + err.Error()) + } + + test.Logf("End Lease Object:%v\n", endLease) + + if !startLease.Equal(endLease) { + byteEndLease, err := json.Marshal(endLease) + if err != nil { + test.Error("Can't Marshal End Lease For Debuging:" + err.Error()) + } + test.Log("End Lease as JSON:" + string(byteEndLease)) + test.Error("Starting Lease Doesn't Match End Lease") + } + +} diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go index 57aa3730..29ff97c2 100644 --- a/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go +++ b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool.go @@ -104,7 +104,7 @@ func (t *MemoryPool) GetNextFreeLease() (bool, leasepool.Lease, error) { defer t.poolLock.Unlock() //Loop Through the elements backwards. - for i := (len(t.pool) - 1); i > 0; i-- { + for i := (len(t.pool) - 1); i >= 0; i-- { //If the Lease Is Free if t.pool[i].Status == leasepool.Free { //Take the Element diff --git a/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool_test.go b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool_test.go new file mode 100644 index 00000000..6e699d66 --- /dev/null +++ b/vendor/github.com/d2g/dhcp4server/leasepool/memorypool/memorypool_test.go @@ -0,0 +1,56 @@ +package memorypool + +import ( + "github.com/d2g/dhcp4" + "github.com/d2g/dhcp4server/leasepool" + "net" + "testing" +) + +func TestLeaseCycle(test *testing.T) { + myMemoryLeasePool := MemoryPool{} + + //Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you. + // So Create Array of IPs 192.168.1.1 to 192.168.1.30 + for i := 0; i < 30; i++ { + err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)}) + if err != nil { + test.Error("Error Creating Lease:" + err.Error()) + } + } + + for i := 0; i < 30; i++ { + hasLease, iLease, err := myMemoryLeasePool.GetNextFreeLease() + if err != nil { + test.Error("Error Getting Lease:" + err.Error()) + } + if !hasLease { + test.Error("Failed to get get lease (none free?)") + } + + if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).Equal(iLease.IP) { + test.Error("Expected Lease:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i).String() + " Received:" + iLease.IP.String()) + } + } +} + +func TestSingleLease(test *testing.T) { + myMemoryLeasePool := MemoryPool{} + + err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0)}) + if err != nil { + test.Error("Error Creating Lease:" + err.Error()) + } + + hasLease, iLease, err := myMemoryLeasePool.GetNextFreeLease() + if err != nil { + test.Error("Error Getting Lease:" + err.Error()) + } + if !hasLease { + test.Error("Failed to get get lease (none free?)") + } + + if !dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0).Equal(iLease.IP) { + test.Error("Expected Lease:" + dhcp4.IPAdd(net.IPv4(192, 168, 1, 5), 0).String() + " Received:" + iLease.IP.String()) + } +} diff --git a/vendor/github.com/d2g/dhcp4server/server.go b/vendor/github.com/d2g/dhcp4server/server.go index c3e5250c..9a4b57ff 100644 --- a/vendor/github.com/d2g/dhcp4server/server.go +++ b/vendor/github.com/d2g/dhcp4server/server.go @@ -5,6 +5,7 @@ import ( "errors" "log" "net" + "sync/atomic" "time" "github.com/d2g/dhcp4" @@ -36,7 +37,7 @@ type Server struct { leasePool leasepool.LeasePool //Lease Pool Manager //Used to Gracefully Close the Server - shutdown bool + shutdown uint32 //Listeners & Response Connection. connection *ipv4.PacketConn } @@ -178,7 +179,7 @@ func (s *Server) ListenAndServe() error { for { ListenForDHCPPackets: - if s.shutdown { + if s.shouldShutdown() { return nil } @@ -191,6 +192,13 @@ func (s *Server) ListenAndServe() error { switch v := err.(type) { case *net.OpError: + // If we've been signaled to shut down, ignore + // the "use of closed network connection" error + // since the connection was closed by the + // shutdown request + if s.shouldShutdown() { + return nil + } if v.Timeout() { goto ListenForDHCPPackets } @@ -204,7 +212,8 @@ func (s *Server) ListenAndServe() error { } } - log.Println("Debug: Unexpect Error from Connection Read From:" + err.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 } @@ -527,7 +536,12 @@ func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Leas * Shutdown The Server Gracefully */ func (s *Server) Shutdown() { - s.shutdown = true + atomic.StoreUint32(&s.shutdown, 1) + s.connection.Close() +} + +func (s *Server) shouldShutdown() bool { + return atomic.LoadUint32(&s.shutdown) == 1 } /* diff --git a/vendor/github.com/d2g/dhcp4server/server_test.go b/vendor/github.com/d2g/dhcp4server/server_test.go new file mode 100644 index 00000000..0080b84c --- /dev/null +++ b/vendor/github.com/d2g/dhcp4server/server_test.go @@ -0,0 +1,426 @@ +package dhcp4server_test + +import ( + "bytes" + "encoding/binary" + "log" + "net" + "sync" + "testing" + "time" + + "github.com/d2g/dhcp4" + "github.com/d2g/dhcp4client" + "github.com/d2g/dhcp4server" + "github.com/d2g/dhcp4server/leasepool" + "github.com/d2g/dhcp4server/leasepool/memorypool" + "github.com/d2g/hardwareaddr" +) + +/* + * Example Server :D + */ +func ExampleServer() { + + //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. + myMemoryLeasePool := memorypool.MemoryPool{} + + //Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you. + // So Create Array of IPs 192.168.1.1 to 192.168.1.30 + for i := 0; i < 30; i++ { + err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)}) + if err != nil { + log.Fatalln("Error Adding IP to pool:" + err.Error()) + } + } + + // We set the port numbers to over 1024 (1067 & 1068) as the automated test don't have root access + tServer, err := dhcp4server.New( + net.IPv4(192, 168, 1, 201), + &myMemoryLeasePool, + dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 1067}), + dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 1068}), + ) + if err != nil { + log.Fatalln("Error Configuring Server:" + err.Error()) + } + + //Start the Server... + err = tServer.ListenAndServe() + if err != nil { + log.Fatalln("Error Starting Server:" + err.Error()) + } +} + +/* + * Test Discovering a Lease That's not Within Our Lease Range. + * This Happens When a devce switches network. + * Example: Mobile Phone on Mobile internet Has IP 100.123.123.123 Switch To Home Wifi + * The device requests 100.123.123.123 on Home Wifi which is out of range... + */ +func TestDiscoverOutOfRangeLease(test *testing.T) { + //Setup the Server + myServer, err := dhcp4server.New( + net.IPv4(192, 168, 1, 201), + getTestLeasePool(), + dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}), + dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), + ) + if err != nil { + test.Error("Error: Can't Configure Server " + err.Error()) + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + err := myServer.ListenAndServe() + if err != nil { + log.Fatalln("Error Starting Server:" + err.Error()) + } + }() + + time.Sleep(time.Duration(5) * time.Second) + + //Generate Hardware Address + HardwareMACAddress, err := hardwareaddr.GenerateEUI48() + if err != nil { + test.Error("Error: Can't Generate Valid MACAddress" + err.Error()) + } + + //Lets Be A Client + + //We need to set the connection ports to 1068 and 1067 so we don't need root access + 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.HardwareAddr(HardwareMACAddress), dhcp4client.Connection(c)) + defer client.Close() + if err != nil { + test.Error("Conection Error:" + err.Error()) + } + + discoveryPacket := client.DiscoverPacket() + discoveryPacket.SetCIAddr(net.IPv4(100, 102, 96, 123)) + discoveryPacket.PadToMinSize() + + err = client.SendPacket(discoveryPacket) + if err != nil { + test.Error("Error: Sending Discover Packet" + err.Error()) + } + + test.Log("--Discovery Packet--") + test.Logf("Client IP : %v\n", discoveryPacket.CIAddr().String()) + test.Logf("Your IP : %v\n", discoveryPacket.YIAddr().String()) + test.Logf("Server IP : %v\n", discoveryPacket.SIAddr().String()) + test.Logf("Gateway IP: %v\n", discoveryPacket.GIAddr().String()) + test.Logf("Client Mac: %v\n", discoveryPacket.CHAddr().String()) + + if !bytes.Equal(discoveryPacket.CHAddr(), HardwareMACAddress) { + test.Error("MACAddresses Don't Match??") + } + + offerPacket, err := client.GetOffer(&discoveryPacket) + if err != nil { + test.Error("Error Getting Offer:" + err.Error()) + } + + test.Log("--Offer Packet--") + test.Logf("Client IP : %v\n", offerPacket.CIAddr().String()) + test.Logf("Your IP : %v\n", offerPacket.YIAddr().String()) + test.Logf("Server IP : %v\n", offerPacket.SIAddr().String()) + test.Logf("Gateway IP: %v\n", offerPacket.GIAddr().String()) + test.Logf("Client Mac: %v\n", offerPacket.CHAddr().String()) + + requestPacket, err := client.SendRequest(&offerPacket) + if err != nil { + test.Error("Error Sending Request:" + err.Error()) + } + + test.Log("--Request Packet--") + test.Logf("Client IP : %v\n", requestPacket.CIAddr().String()) + test.Logf("Your IP : %v\n", requestPacket.YIAddr().String()) + test.Logf("Server IP : %v\n", requestPacket.SIAddr().String()) + test.Logf("Gateway IP: %v\n", requestPacket.GIAddr().String()) + test.Logf("Client Mac: %v\n", requestPacket.CHAddr().String()) + + acknowledgement, err := client.GetAcknowledgement(&requestPacket) + if err != nil { + test.Error("Error Getting Acknowledgement:" + err.Error()) + } + + test.Log("--Acknowledgement Packet--") + test.Logf("Client IP : %v\n", acknowledgement.CIAddr().String()) + test.Logf("Your IP : %v\n", acknowledgement.YIAddr().String()) + test.Logf("Server IP : %v\n", acknowledgement.SIAddr().String()) + test.Logf("Gateway IP: %v\n", acknowledgement.GIAddr().String()) + test.Logf("Client Mac: %v\n", acknowledgement.CHAddr().String()) + + acknowledgementOptions := acknowledgement.ParseOptions() + if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK { + test.Error("Didn't get ACK?:" + err.Error()) + } + + test.Log("Shutting Down Server") + myServer.Shutdown() + wg.Wait() +} + +/* + * Try Renewing A Lease From A Different Network. + */ +func TestRequestOutOfRangeLease(test *testing.T) { + //Setup the Server + myServer, err := dhcp4server.New( + net.IPv4(192, 168, 1, 201), + getTestLeasePool(), + dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1067}), + dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1068}), + ) + if err != nil { + test.Error("Error: Can't Configure Server " + err.Error()) + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + + err := myServer.ListenAndServe() + if err != nil { + log.Fatalln("Error Starting Server:" + err.Error()) + } + }() + + //Sleep some so the server starts.... + time.Sleep(time.Duration(5) * time.Second) + + //Generate Hardware Address + HardwareMACAddress, err := hardwareaddr.GenerateEUI48() + if err != nil { + test.Error("Error: Can't Generate Valid MACAddress" + err.Error()) + } + + HardwareMACAddress, err = net.ParseMAC("58-94-6B-73-57-0C") + if err != nil { + log.Printf("MAC Error:%v\n", err) + } + + //Lets Be A Client + 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.HardwareAddr(HardwareMACAddress), dhcp4client.Connection(c)) + defer client.Close() + + if err != nil { + test.Error("Conection Error:" + err.Error()) + } + + //Create a dummy offer packet + offerPacket := client.DiscoverPacket() + + offerPacket.SetCIAddr(net.IPv4(100, 102, 96, 123)) + offerPacket.SetSIAddr(net.IPv4(192, 168, 1, 201)) + offerPacket.SetYIAddr(net.IPv4(100, 102, 96, 123)) + offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)}) + + requestPacket, err := client.SendRequest(&offerPacket) + if err != nil { + test.Error("Error Sending Request:" + err.Error()) + } + + test.Log("--Request Packet--") + test.Logf("Client IP : %v\n", requestPacket.CIAddr().String()) + test.Logf("Your IP : %v\n", requestPacket.YIAddr().String()) + test.Logf("Server IP : %v\n", requestPacket.SIAddr().String()) + test.Logf("Gateway IP: %v\n", requestPacket.GIAddr().String()) + test.Logf("Client Mac: %v\n", requestPacket.CHAddr().String()) + + acknowledgement, err := client.GetAcknowledgement(&requestPacket) + if err != nil { + test.Error("Error Getting Acknowledgement:" + err.Error()) + } + + test.Log("--Acknowledgement Packet--") + test.Logf("Client IP : %v\n", acknowledgement.CIAddr().String()) + test.Logf("Your IP : %v\n", acknowledgement.YIAddr().String()) + test.Logf("Server IP : %v\n", acknowledgement.SIAddr().String()) + test.Logf("Gateway IP: %v\n", acknowledgement.GIAddr().String()) + test.Logf("Client Mac: %v\n", acknowledgement.CHAddr().String()) + + acknowledgementOptions := acknowledgement.ParseOptions() + if len(acknowledgementOptions[dhcp4.OptionDHCPMessageType]) <= 0 || dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.NAK { + test.Errorf("Didn't get NAK got DHCP4 Message Type:%v\n", dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0])) + } + + test.Log("Shutting Down Server") + myServer.Shutdown() + wg.Wait() +} + +/* + * + */ +func TestConsumeLeases(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() + + for i := 0; i < 30; i++ { + //Generate Hardware Address + HardwareMACAddress, err := hardwareaddr.GenerateEUI48() + if err != nil { + test.Error("Error: Can't Generate Valid MACAddress" + err.Error()) + } + + client.SetOption(dhcp4client.HardwareAddr(HardwareMACAddress)) + test.Log("MAC:" + HardwareMACAddress.String()) + + discovery := client.DiscoverPacket() + + //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) + 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()) + } + + //How long the lease is for? + acknowledgementOptions := acknowledgement.ParseOptions() + if len(acknowledgementOptions) > 0 { + test.Logf("Lease Options:%v\n", acknowledgementOptions) + if acknowledgementOptions[dhcp4.OptionIPAddressLeaseTime] != nil { + var result uint32 + buf := bytes.NewBuffer(acknowledgementOptions[dhcp4.OptionIPAddressLeaseTime]) + binary.Read(buf, binary.BigEndian, &result) + test.Logf("Lease Time (Seconds):%d\n", result) + } + } else { + test.Errorf("Lease:\"%v\" Has No Options\n", acknowledgement.YIAddr()) + } + } +} + +/* + * Benchmark the ServeDHCP Function + */ +func BenchmarkServeDHCP(test *testing.B) { + //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. + myMemoryLeasePool := memorypool.MemoryPool{} + + //Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you. + // So Create Array of IPs 192.168.1.1 to 192.168.1.30 + for i := 0; i < test.N; i++ { + err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)}) + if err != nil { + log.Fatalln("Error Adding IP to pool:" + err.Error()) + } + } + + //Setup the Server + myServer, err := dhcp4server.New( + net.IPv4(127, 0, 0, 1), + &myMemoryLeasePool, + ) + 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() + + test.ResetTimer() + + for i := 0; i < test.N; i++ { + test.StopTimer() + //Generate Hardware Address + HardwareMACAddress, err := hardwareaddr.GenerateEUI48() + if err != nil { + test.Error("Error: Can't Generate Valid MACAddress" + err.Error()) + } + + client.SetOption(dhcp4client.HardwareAddr(HardwareMACAddress)) + discovery := client.DiscoverPacket() + + //Run the Discovery On the Server + test.StartTimer() + offer, err := myServer.ServeDHCP(discovery) + if err != nil { + test.Error("Discovery Error:" + err.Error()) + } + + if len(offer) == 0 { + test.Error("No Valid Offer") + } else { + request := client.RequestPacket(&offer) + _, err := myServer.ServeDHCP(request) + if err != nil { + test.Error("Acknowledge Error:" + err.Error()) + } + + } + } +} + +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. + myMemoryLeasePool := memorypool.MemoryPool{} + + //Lets add a list of IPs to the pool these will be served to the clients so make sure they work for you. + // So Create Array of IPs 192.168.1.1 to 192.168.1.30 + for i := 0; i < 30; i++ { + err := myMemoryLeasePool.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, 1), i)}) + if err != nil { + log.Fatalln("Error Adding IP to pool:" + err.Error()) + } + } + return &myMemoryLeasePool +}