diff --git a/dhcp/dhcp.go b/dhcp/dhcp.go index 2a034e1..724edc5 100644 --- a/dhcp/dhcp.go +++ b/dhcp/dhcp.go @@ -40,8 +40,8 @@ const confTemplate = ` server6: plugins: - server_id: LL {{ .IntfMacAddr }} - {{ if .BootzURL }} - - bootz: {{ .BootzURL }} + {{ if .BootzURLs }} + - bootz: {{ .BootzURLs }} {{ end }} {{ if .DNSv6 }} - DNS: {{ .DNSv6 }} @@ -53,8 +53,8 @@ server4: plugins: - lease_time: 3600s - server_id: {{ .IntfIPAddr }} - {{ if .BootzURL }} - - bootz: {{ .BootzURL }} + {{ if .BootzURLs }} + - bootz: {{ .BootzURLs }} {{ end }} {{ if .DNSv4 }} - DNS: {{ .DNSv4 }} @@ -77,7 +77,7 @@ type Config struct { Interface string DNS []string AddressMap map[string]*Entry - BootzURL string + BootzURLs []string } // Entry represents a dhcp record. @@ -158,7 +158,7 @@ func generateConfigFile(conf *Config) (string, error) { intf, err := net.InterfaceByName(conf.Interface) if err != nil { - return "", fmt.Errorf("unknown interface %v", *intf) + return "", fmt.Errorf("unknown interface %v: %v", conf.Interface, err) } IPv4Addr := getIPv4Address(intf) @@ -191,7 +191,7 @@ func generateConfigFile(conf *Config) (string, error) { DNSv6 string IPv4Leases string IPv6Leases string - BootzURL string + BootzURLs string }{ IntfIPAddr: IPv4Addr.String(), IntfMacAddr: intf.HardwareAddr.String(), @@ -199,7 +199,7 @@ func generateConfigFile(conf *Config) (string, error) { DNSv6: strings.Join(DNSv6, " "), IPv4Leases: strings.Join(v4Records, " "), IPv6Leases: strings.Join(v6Records, " "), - BootzURL: conf.BootzURL, + BootzURLs: strings.Join(conf.BootzURLs, " "), }); err != nil { return "", fmt.Errorf("error generating configuration template: %v", err) } diff --git a/dhcp/main/dhcp.go b/dhcp/main/dhcp.go index 5fc16e3..416bb1e 100644 --- a/dhcp/main/dhcp.go +++ b/dhcp/main/dhcp.go @@ -29,7 +29,7 @@ var ( intf = flag.String("i", "eth7", "Network interface to use for dhcp server.") records = flag.String("records", "4c:5d:3c:ef:de:60,5.78.26.27/16,5.78.0.1;FOX2506P2QT,5::10/64", "List of dhcp records separated by a semi-colon.") dns = flag.String("dns", "5.38.4.124", "List of dns servers separated by a semi-colon.") - bootz = flag.String("bootz_url", "bootz://dev-mgbl-lnx6.cisco.com:50052/grpc", "Bootz server URL.") + bootz = flag.String("bootz_urls", "bootz://dev-mgbl-lnx6.cisco.com:50052/grpc;bootz://dev-mgbl-lnx2.cisco.com:50052/grpc", "List of Bootz server URLs separated by a semi-colon.") ) func main() { @@ -57,7 +57,7 @@ func main() { Interface: *intf, DNS: strings.Split(*dns, ";"), AddressMap: addressMap, - BootzURL: *bootz, + BootzURLs: strings.Split(*bootz, ";"), } go func() { diff --git a/dhcp/plugins/bootz/bootz.go b/dhcp/plugins/bootz/bootz.go index 98ddd48..684c810 100644 --- a/dhcp/plugins/bootz/bootz.go +++ b/dhcp/plugins/bootz/bootz.go @@ -16,6 +16,7 @@ package bootz import ( + "encoding/binary" "fmt" "net/url" @@ -44,33 +45,60 @@ var ( ztpV6Opt dhcpv6.Option ) -func parseArgs(args ...string) (*url.URL, error) { - if len(args) != 1 { - return nil, fmt.Errorf("exactly one argument must be passed to BootZ plugin, got %d", len(args)) +func encodeBootstrapServerList(urls []string) []byte { + // From RFC 8572 section 8.3: + // + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+ + // | uri-length | URI | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-...-+-+-+-+-+-+-+ + // + // * uri-length: 2 octets long; specifies the length of the URI data. + // * URI: URI of the SZTP bootstrap server. + b := []byte{} + for _, u := range urls { + b = binary.BigEndian.AppendUint16(b, uint16(len(u))) + b = append(b, []byte(u)...) } - return url.Parse(args[0]) + return b +} + +// Verifies that the passed Bootz servers are valid URLs and returns them as a list of strings. +func parseArgs(args ...string) ([]string, error) { + if len(args) < 1 { + return nil, fmt.Errorf("at least one argument must be passed to BootZ plugin, got %d", len(args)) + } + urls := make([]string, len(args)) + for i, arg := range args { + u, err := url.Parse(arg) + if err != nil { + return nil, err + } + urls[i] = u.String() + } + return urls, nil } func setup4(args ...string) (handler.Handler4, error) { - url, err := parseArgs(args...) + urls, err := parseArgs(args...) if err != nil { return nil, err } + ztpV4Opt = &dhcpv4.Option{ Code: dhcpv4.GenericOptionCode(OPTION_V4_SZTP_REDIRECT), - Value: dhcpv4.String(url.String()), + Value: dhcpv4.String(string(encodeBootstrapServerList(urls))), } return handler4, nil } func setup6(args ...string) (handler.Handler6, error) { - url, err := parseArgs(args...) + urls, err := parseArgs(args...) if err != nil { return nil, err } ztpV6Opt = &dhcpv6.OptionGeneric{ OptionCode: dhcpv6.OptionCode(OPTION_V6_SZTP_REDIRECT), - OptionData: []byte(url.String()), + OptionData: encodeBootstrapServerList(urls), } return handler6, nil }