From 2c42665a0bcaa2495fab477a3dfefabd13db0036 Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Mon, 23 Oct 2017 14:40:10 -0700 Subject: [PATCH] added ipv6api command --- dhcp6/boot_configuration.go | 18 +++++------ dhcp6/packet.go | 52 ++++++++++++++++---------------- dhcp6/packet_test.go | 58 +++++++++++++++++++++++------------- pixiecore/cli/bootipv6cmd.go | 4 +-- pixiecore/cli/ipv6apicmd.go | 55 ++++++++++++++++++++++++++++++++++ pixiecorev6/dhcpv6.go | 10 ++++++- pixiecorev6/pixicorev6.go | 13 ++++---- 7 files changed, 145 insertions(+), 65 deletions(-) create mode 100644 pixiecore/cli/ipv6apicmd.go diff --git a/dhcp6/boot_configuration.go b/dhcp6/boot_configuration.go index ecc8122..3cd2b8b 100644 --- a/dhcp6/boot_configuration.go +++ b/dhcp6/boot_configuration.go @@ -10,19 +10,19 @@ import ( ) type BootConfiguration interface { - GetBootUrl(id []byte, clientArchType uint16) (string, error) + GetBootUrl(id []byte, clientArchType uint16) ([]byte, error) } type StaticBootConfiguration struct { - HttpBootUrl string - IPxeBootUrl string + HttpBootUrl []byte + IPxeBootUrl []byte } func MakeStaticBootConfiguration(httpBootUrl, ipxeBootUrl string) *StaticBootConfiguration { - return &StaticBootConfiguration{HttpBootUrl: httpBootUrl, IPxeBootUrl: ipxeBootUrl} + return &StaticBootConfiguration{HttpBootUrl: []byte(httpBootUrl), IPxeBootUrl: []byte(ipxeBootUrl)} } -func (bc *StaticBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) (string, error) { +func (bc *StaticBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) ([]byte, error) { if 0x10 == clientArchType { return bc.HttpBootUrl, nil } @@ -44,15 +44,15 @@ func MakeApiBootConfiguration(url string, timeout time.Duration) *ApiBootConfigu } } -func (bc *ApiBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) (string, error) { +func (bc *ApiBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) ([]byte, error) { reqURL := fmt.Sprintf("%s/boot/%x/%d", bc.urlPrefix, id, clientArchType) resp, err := bc.client.Get(reqURL) if err != nil { - return "", err + return nil, err } if resp.StatusCode != http.StatusOK { resp.Body.Close() - return "", fmt.Errorf("%s: %s", reqURL, http.StatusText(resp.StatusCode)) + return nil, fmt.Errorf("%s: %s", reqURL, http.StatusText(resp.StatusCode)) } defer resp.Body.Close() @@ -60,7 +60,7 @@ func (bc *ApiBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) (st buf.ReadFrom(resp.Body) url, _ := bc.makeURLAbsolute(buf.String()) - return url, nil + return []byte(url), nil } func (bc *ApiBootConfiguration) makeURLAbsolute(urlStr string) (string, error) { diff --git a/dhcp6/packet.go b/dhcp6/packet.go index 47ce883..c9d9f72 100644 --- a/dhcp6/packet.go +++ b/dhcp6/packet.go @@ -128,28 +128,41 @@ func ShouldDiscardInformationRequest(p *Packet, serverDuid []byte) error { return nil } -func (b *PacketBuilder) BuildResponse(in *Packet) *Packet { +func (b *PacketBuilder) BuildResponse(in *Packet) (*Packet, error) { switch in.Type { case MsgSolicit: association := b.Addresses.ReserveAddress(in.Options.ClientId(), in.Options.IaNaId()) + bootFileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType()) + if err != nil { + return nil, err + } return b.MakeMsgAdvertise(in.TransactionID, in.Options.ClientId(), in.Options.IaNaId(), - in.Options.ClientArchType(), association.IpAddress) + in.Options.ClientArchType(), association.IpAddress, bootFileUrl), nil case MsgRequest: association := b.Addresses.ReserveAddress(in.Options.ClientId(), in.Options.IaNaId()) + bootFileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType()) + if err != nil { + return nil, err + } return b.MakeMsgReply(in.TransactionID, in.Options.ClientId(), in.Options.IaNaId(), - in.Options.ClientArchType(), association.IpAddress) + in.Options.ClientArchType(), association.IpAddress, bootFileUrl), nil case MsgInformationRequest: + bootFileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType()) + if err != nil { + return nil, err + } return b.MakeMsgInformationRequestReply(in.TransactionID, in.Options.ClientId(), - in.Options.ClientArchType()) + in.Options.ClientArchType(), bootFileUrl), nil case MsgRelease: b.Addresses.ReleaseAddress(in.Options.ClientId(), in.Options.IaNaId()) - return b.MakeMsgReleaseReply(in.TransactionID, in.Options.ClientId()) + return b.MakeMsgReleaseReply(in.TransactionID, in.Options.ClientId()), nil default: - return nil + return nil, nil } } -func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte) *Packet { +func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte, + bootFileUrl []byte) *Packet { ret_options := make(Options) ret_options.AddOption(MakeOption(OptClientId, clientId)) ret_options.AddOption(MakeIaNaOption(iaId, b.calculateT1(), b.calculateT2(), @@ -158,12 +171,7 @@ func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId [ if 0x10 == clientArchType { // HTTPClient ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient } - bootfileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(clientId), clientArchType) - if err != nil { - fmt.Printf("!!!!!!!!!!!!!!!!!!!!!!!! %s", err) - return nil - } - ret_options.AddOption(MakeOption(OptBootfileUrl, []byte(bootfileUrl))) + ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl)) // ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1")) //ret_options.AddOption(OptBootfileParam, []byte("http://") @@ -172,7 +180,8 @@ func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId [ return &Packet{Type: MsgAdvertise, TransactionID: transactionId, Options: ret_options} } -func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte) *Packet { +func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte, + bootFileUrl []byte) *Packet { ret_options := make(Options) ret_options.AddOption(MakeOption(OptClientId, clientId)) ret_options.AddOption(MakeIaNaOption(iaId, b.calculateT1(), b.calculateT2(), @@ -181,27 +190,20 @@ func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byt if 0x10 == clientArchType { // HTTPClient ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient } - bootfileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(clientId), clientArchType) - if err != nil { - return nil - } - ret_options.AddOption(MakeOption(OptBootfileUrl, []byte(bootfileUrl))) + ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl)) return &Packet{Type: MsgReply, TransactionID: transactionId, Options: ret_options} } -func (b *PacketBuilder) MakeMsgInformationRequestReply(transactionId [3]byte, clientId []byte, clientArchType uint16) *Packet { +func (b *PacketBuilder) MakeMsgInformationRequestReply(transactionId [3]byte, clientId []byte, clientArchType uint16, + bootFileUrl []byte) *Packet { ret_options := make(Options) ret_options.AddOption(MakeOption(OptClientId, clientId)) ret_options.AddOption(MakeOption(OptServerId, b.ServerDuid)) if 0x10 == clientArchType { // HTTPClient ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient } - bootfileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(clientId), clientArchType) - if err != nil { - return nil - } - ret_options.AddOption(MakeOption(OptBootfileUrl, []byte(bootfileUrl))) + ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl)) return &Packet{Type: MsgReply, TransactionID: transactionId, Options: ret_options} } diff --git a/dhcp6/packet_test.go b/dhcp6/packet_test.go index a17e3cd..065092b 100644 --- a/dhcp6/packet_test.go +++ b/dhcp6/packet_test.go @@ -11,12 +11,12 @@ func TestMakeMsgAdvertise(t *testing.T) { expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x11, expectedIp) + msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x11, expectedIp, expectedBootFileUrl) if msg.Type != MsgAdvertise { t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type) @@ -51,6 +51,9 @@ func TestMakeMsgAdvertise(t *testing.T) { if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } iaNaOption := msg.Options[OptIaNa] if iaNaOption == nil { @@ -63,22 +66,24 @@ func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) { expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x10, expectedIp) + msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x10, expectedIp, expectedBootFileUrl) vendorClassOption := msg.Options[OptVendorClass] if vendorClassOption == nil { t.Fatalf("Vendor class option should be present") } - bootfileUrlOption := msg.Options[OptBootfileUrl] if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } } func TestMakeMsgReply(t *testing.T) { @@ -86,12 +91,12 @@ func TestMakeMsgReply(t *testing.T) { expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgReply(transactionId, expectedClientId, []byte("1234"), 0x11, expectedIp) + msg := builder.MakeMsgReply(transactionId, expectedClientId, []byte("1234"), 0x11, expectedIp, expectedBootFileUrl) if msg.Type != MsgReply { t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type) @@ -126,6 +131,9 @@ func TestMakeMsgReply(t *testing.T) { if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } iaNaOption := msg.Options[OptIaNa] if iaNaOption == nil { @@ -138,12 +146,12 @@ func TestMakeMsgReplyWithHttpClientArch(t *testing.T) { expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgReply(transactionId, expectedClientId, []byte("1234"), 0x10, expectedIp) + msg := builder.MakeMsgReply(transactionId, expectedClientId, []byte("1234"), 0x10, expectedIp, expectedBootFileUrl) vendorClassOption := msg.Options[OptVendorClass] if vendorClassOption == nil { @@ -154,18 +162,21 @@ func TestMakeMsgReplyWithHttpClientArch(t *testing.T) { if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } } func TestMakeMsgInformationRequestReply(t *testing.T) { expectedClientId := []byte("clientid") expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x11) + msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x11, expectedBootFileUrl) if msg.Type != MsgReply { t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type) @@ -200,18 +211,21 @@ func TestMakeMsgInformationRequestReply(t *testing.T) { if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } } func TestMakeMsgInformationRequestReplyWithHttpClientArch(t *testing.T) { expectedClientId := []byte("clientid") expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} + expectedBootFileUrl := []byte("http://bootfileurl") - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) - msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x10) + msg := builder.MakeMsgInformationRequestReply(transactionId, expectedClientId, 0x10, expectedBootFileUrl) vendorClassOption := msg.Options[OptVendorClass] if vendorClassOption == nil { @@ -222,6 +236,9 @@ func TestMakeMsgInformationRequestReplyWithHttpClientArch(t *testing.T) { if bootfileUrlOption == nil { t.Fatalf("Bootfile URL option should be present") } + if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) { + t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption) + } } func TestMakeMsgReleaseReply(t *testing.T) { @@ -229,8 +246,7 @@ func TestMakeMsgReleaseReply(t *testing.T) { expectedServerId := []byte("serverid") transactionId := [3]byte{'1', '2', '3'} - bootConfig := MakeStaticBootConfiguration("httpbootfileurl", "ipxebootfileurl") - builder := MakePacketBuilder(expectedServerId, 90, 100, bootConfig, + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100)) msg := builder.MakeMsgReleaseReply(transactionId, expectedClientId) diff --git a/pixiecore/cli/bootipv6cmd.go b/pixiecore/cli/bootipv6cmd.go index 6a4825f..7c40e31 100644 --- a/pixiecore/cli/bootipv6cmd.go +++ b/pixiecore/cli/bootipv6cmd.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/cobra" "fmt" "go.universe.tf/netboot/pixiecorev6" + "go.universe.tf/netboot/dhcp6" ) var bootIPv6Cmd = &cobra.Command{ @@ -37,8 +38,7 @@ var bootIPv6Cmd = &cobra.Command{ } s.Address = addr - s.IPxeUrl = ipxeUrl - s.HttpbootUrl = httpBootUrl + s.BootUrls = dhcp6.MakeStaticBootConfiguration(httpBootUrl, ipxeUrl) fmt.Println(s.Serve()) }, diff --git a/pixiecore/cli/ipv6apicmd.go b/pixiecore/cli/ipv6apicmd.go new file mode 100644 index 0000000..1913db8 --- /dev/null +++ b/pixiecore/cli/ipv6apicmd.go @@ -0,0 +1,55 @@ +package cli + +import ( + "github.com/spf13/cobra" + "fmt" + "go.universe.tf/netboot/pixiecorev6" + "go.universe.tf/netboot/dhcp6" + "time" +) + +var ipv6ApiCmd = &cobra.Command{ + Use: "ipv6api", + Short: "Boot a kernel and optional init ramdisks over IPv6 using api", + Run: func(cmd *cobra.Command, args []string) { + addr, err := cmd.Flags().GetString("listen-addr") + if err != nil { + fatalf("Error reading flag: %s", err) + } + apiUrl, err := cmd.Flags().GetString("api-request-url") + if err != nil { + fatalf("Error reading flag: %s", err) + } + apiTimeout, err := cmd.Flags().GetDuration("api-request-timeout") + if err != nil { + fatalf("Error reading flag: %s", err) + } + + s := pixiecorev6.NewServerV6() + + if addr == "" { + fatalf("Please specify address to listen on") + } else { + } + if apiUrl == "" { + fatalf("Please specify ipxe config file url") + } + + s.Address = addr + s.BootUrls = dhcp6.MakeApiBootConfiguration(apiUrl, apiTimeout) + + fmt.Println(s.Serve()) + }, +} + +func serverv6ApiConfigFlags(cmd *cobra.Command) { + cmd.Flags().StringP("listen-addr", "", "", "IPv6 address to listen on") + cmd.Flags().StringP("api-request-url", "", "", "Ipv6-specific API server url") +} + +func init() { + rootCmd.AddCommand(ipv6ApiCmd) + serverv6ApiConfigFlags(ipv6ApiCmd) + ipv6ApiCmd.Flags().Duration("api-request-timeout", 5*time.Second, "Timeout for request to the API server") +} + diff --git a/pixiecorev6/dhcpv6.go b/pixiecorev6/dhcpv6.go index 6558484..748f0e8 100644 --- a/pixiecorev6/dhcpv6.go +++ b/pixiecorev6/dhcpv6.go @@ -19,7 +19,15 @@ func (s *ServerV6) serveDHCP(conn *dhcp6.Conn, packetBuilder *dhcp6.PacketBuilde s.log("dhcpv6", fmt.Sprintf("Received (%d) packet (%d): %s\n", pkt.Type, pkt.TransactionID, pkt.Options.HumanReadable())) - response := packetBuilder.BuildResponse(pkt) + response, err := packetBuilder.BuildResponse(pkt) + if err != nil { + s.log("dhcpv6", fmt.Sprintf("Error creating response for transaction: %s: %s", pkt.TransactionID, err)) + continue + } + if response == nil { + s.log("dhcpv6", fmt.Sprintf("Don't know how to respond to packet type: %d (transaction id %s)", pkt.Type, pkt.TransactionID)) + continue + } marshalled_response, err := response.Marshal() if err != nil { diff --git a/pixiecorev6/pixicorev6.go b/pixiecorev6/pixicorev6.go index 86991fb..55007be 100644 --- a/pixiecorev6/pixicorev6.go +++ b/pixiecorev6/pixicorev6.go @@ -10,11 +10,10 @@ import ( ) type ServerV6 struct { - Address string - Port string - Duid []byte - IPxeUrl string - HttpbootUrl string + Address string + Port string + Duid []byte + BootUrls dhcp6.BootConfiguration errs chan error @@ -56,8 +55,8 @@ func (s *ServerV6) Serve() error { addressPool := dhcp6.NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::10"), net.ParseIP("2001:db8:f00f:cafe::100"), 1850) // bootConfiguration := dhcp6.MakeStaticBootConfiguration("http://[2001:db8:f00f:cafe::4]/bootx64.efi", "http://[2001:db8:f00f:cafe::4]/script.ipxe") - bootConfiguration := dhcp6.MakeApiBootConfiguration("http://[2001:db8:f00f:cafe::4]:8888/", 10 *time.Second) - packetBuilder := dhcp6.MakePacketBuilder(s.Duid, 1800, 1850, bootConfiguration, addressPool) +// bootConfiguration := dhcp6.MakeApiBootConfiguration("http://[2001:db8:f00f:cafe::4]:8888/", 10 *time.Second) + packetBuilder := dhcp6.MakePacketBuilder(s.Duid, 1800, 1850, s.BootUrls, addressPool) go func() { s.errs <- s.serveDHCP(dhcp, packetBuilder) }()