From cdf061fe7b884a1d6958fc93dda326b56afee1fc Mon Sep 17 00:00:00 2001 From: Damyan Yordanov Date: Wed, 17 Apr 2024 10:42:24 +0200 Subject: [PATCH] Add PXE/TFTP IPv6 tests --- plugins/pxeboot/{pxeboot.go => plugin.go} | 6 +- plugins/pxeboot/plugin_test.go | 238 ++++++++++++++++++++++ 2 files changed, 242 insertions(+), 2 deletions(-) rename plugins/pxeboot/{pxeboot.go => plugin.go} (97%) create mode 100644 plugins/pxeboot/plugin_test.go diff --git a/plugins/pxeboot/pxeboot.go b/plugins/pxeboot/plugin.go similarity index 97% rename from plugins/pxeboot/pxeboot.go rename to plugins/pxeboot/plugin.go index bd4b0f6..6f3cf86 100644 --- a/plugins/pxeboot/pxeboot.go +++ b/plugins/pxeboot/plugin.go @@ -20,9 +20,11 @@ package pxeboot import ( "fmt" - "github.com/insomniacslk/dhcp/dhcpv4" "net/url" + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/iana" + "github.com/coredhcp/coredhcp/handler" "github.com/coredhcp/coredhcp/logger" "github.com/coredhcp/coredhcp/plugins" @@ -153,7 +155,7 @@ func pxeBootHandler6(req, resp dhcpv6.DHCPv6) (dhcpv6.DHCPv6, bool) { if decap.GetOneOption(dhcpv6.OptionClientArchType) != nil { optBytes := decap.GetOneOption(dhcpv6.OptionClientArchType).ToBytes() log.Debugf("ClientArchType: %s (%x)", string(optBytes), optBytes) - if len(optBytes) == 2 && optBytes[0] == 0 && optBytes[1] == 0x07 { + if len(optBytes) == 2 && optBytes[0] == 0 && optBytes[1] == byte(iana.EFI_X86_64) { // 0x07 opt = &tftpOption } } diff --git a/plugins/pxeboot/plugin_test.go b/plugins/pxeboot/plugin_test.go new file mode 100644 index 0000000..6b19ccc --- /dev/null +++ b/plugins/pxeboot/plugin_test.go @@ -0,0 +1,238 @@ +// Copyright 2018-present the CoreDHCP Authors. All rights reserved +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package pxeboot + +import ( + "testing" + + "github.com/insomniacslk/dhcp/dhcpv6" + "github.com/insomniacslk/dhcp/iana" +) + +const ( + ipxePath = "http://[2001:db8::1]/boot.ipxe" + tftpPath = "tftp://[2001:db8::1]/boot.efi" +) + +var ( + numberOptsBootFileURL int +) + +func Init(numOptBoot int) { + numberOptsBootFileURL = numOptBoot + + _, err := setup6(tftpPath, ipxePath) + if err != nil { + log.Fatal(err) + } +} + +func TestPXERequested6(t *testing.T) { + Init(1) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + optUserClass := dhcpv6.OptUserClass{} + buf := []byte{ + 0, 4, + 'i', 'P', 'X', 'E', + } + _ = optUserClass.FromBytes(buf) + req.UpdateOption(&optUserClass) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } + + bootFileURL := resp.(*dhcpv6.Message).Options.BootFileURL() + if bootFileURL != ipxePath { + t.Errorf("Found BootFileURL %s, expected %s", bootFileURL, ipxePath) + } +} + +func TestTFTPRequested6(t *testing.T) { + Init(1) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + optClientArchType := dhcpv6.OptClientArchType(iana.EFI_X86_64) + req.UpdateOption(optClientArchType) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } + + bootFileURL := resp.(*dhcpv6.Message).Options.BootFileURL() + if bootFileURL != tftpPath { + t.Errorf("Found BootFileURL %s, expected %s", bootFileURL, tftpPath) + } +} + +func TestWrongPXERequested6(t *testing.T) { + Init(0) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + optUserClass := dhcpv6.OptUserClass{} + buf := []byte{ + 0, 6, + 'f', '0', '0', 'b', 'a', 'r', // nonsense + } + _ = optUserClass.FromBytes(buf) + req.UpdateOption(&optUserClass) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } +} + +func TestWrongTFTPRequested6(t *testing.T) { + Init(0) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + optClientArchType := dhcpv6.OptClientArchType(iana.Arch(4711)) // nonsense + req.UpdateOption(optClientArchType) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } +} + +func TestPXENotRequested6(t *testing.T) { + Init(0) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } +} + +func TestTFTPNotRequested6(t *testing.T) { + Init(0) + + req, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + req.MessageType = dhcpv6.MessageTypeRequest + req.AddOption(dhcpv6.OptRequestedOption(dhcpv6.OptionBootfileURL)) + + stub, err := dhcpv6.NewMessage() + if err != nil { + t.Fatal(err) + } + stub.MessageType = dhcpv6.MessageTypeReply + + resp, stop := pxeBootHandler6(req, stub) + if resp == nil { + t.Fatal("plugin did not return a message") + } + + if !stop { + t.Error("plugin does not interrupt processing, but it should have") + } + opts := resp.GetOption(dhcpv6.OptionBootfileURL) + if len(opts) != numberOptsBootFileURL { + t.Fatalf("Expected %d BootFileUrl option, got %d: %v", numberOptsBootFileURL, len(opts), opts) + } +}