added support for setting server preference option

This commit is contained in:
Dmitri Dolguikh 2017-10-24 16:18:43 -07:00 committed by Dave Anderson
parent 4d45c38c40
commit f89f6af9a6
7 changed files with 103 additions and 31 deletions

View File

@ -11,15 +11,25 @@ import (
type BootConfiguration interface {
GetBootUrl(id []byte, clientArchType uint16) ([]byte, error)
GetPreference() []byte
GetRecursiveDns() []byte
}
type StaticBootConfiguration struct {
HttpBootUrl []byte
IPxeBootUrl []byte
HttpBootUrl []byte
IPxeBootUrl []byte
RecursiveDns []byte
Preference []byte
UsePreference bool
}
func MakeStaticBootConfiguration(httpBootUrl, ipxeBootUrl string) *StaticBootConfiguration {
return &StaticBootConfiguration{HttpBootUrl: []byte(httpBootUrl), IPxeBootUrl: []byte(ipxeBootUrl)}
func MakeStaticBootConfiguration(httpBootUrl, ipxeBootUrl string, preference uint8, usePreference bool) *StaticBootConfiguration {
ret := &StaticBootConfiguration{HttpBootUrl: []byte(httpBootUrl), IPxeBootUrl: []byte(ipxeBootUrl), UsePreference: usePreference}
if usePreference {
ret.Preference = make([]byte, 1)
ret.Preference[0] = byte(preference)
}
return ret
}
func (bc *StaticBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) ([]byte, error) {
@ -29,19 +39,37 @@ func (bc *StaticBootConfiguration) GetBootUrl(id []byte, clientArchType uint16)
return bc.IPxeBootUrl, nil
}
type ApiBootConfiguration struct {
client *http.Client
urlPrefix string
func (bc *StaticBootConfiguration) GetPreference() []byte {
return bc.Preference
}
func MakeApiBootConfiguration(url string, timeout time.Duration) *ApiBootConfiguration {
func (bc *StaticBootConfiguration) GetRecursiveDns() []byte {
return bc.RecursiveDns
}
type ApiBootConfiguration struct {
client *http.Client
urlPrefix string
RecursiveDns []byte
Preference []byte
UsePreference bool
}
func MakeApiBootConfiguration(url string, timeout time.Duration, preference uint8, usePreference bool) *ApiBootConfiguration {
if !strings.HasSuffix(url, "/") {
url += "/"
}
return &ApiBootConfiguration{
ret := &ApiBootConfiguration{
client: &http.Client{Timeout: timeout},
urlPrefix: url + "v1",
UsePreference: usePreference,
}
if usePreference {
ret.Preference = make([]byte, 1)
ret.Preference[0] = byte(preference)
}
return ret
}
func (bc *ApiBootConfiguration) GetBootUrl(id []byte, clientArchType uint16) ([]byte, error) {
@ -77,3 +105,11 @@ func (bc *ApiBootConfiguration) makeURLAbsolute(urlStr string) (string, error) {
}
return u.String(), nil
}
func (bc *ApiBootConfiguration) GetPreference() []byte {
return bc.Preference
}
func (bc *ApiBootConfiguration) GetRecursiveDns() []byte {
return bc.RecursiveDns
}

View File

@ -34,7 +34,7 @@ type PacketBuilder struct {
ServerDuid []byte
PreferredLifetime uint32
ValidLifetime uint32
BootFileUrl BootConfiguration
Configuration BootConfiguration
Addresses AddressPool
}
@ -51,7 +51,7 @@ func MakePacket(bs []byte, packetLength int) (*Packet, error) {
func MakePacketBuilder(serverDuid []byte, preferredLifetime, validLifetime uint32, bootFileUrl BootConfiguration,
addressPool AddressPool) *PacketBuilder {
return &PacketBuilder{ServerDuid: serverDuid, PreferredLifetime: preferredLifetime, ValidLifetime: validLifetime,
BootFileUrl: bootFileUrl, Addresses: addressPool}
Configuration: bootFileUrl, Addresses: addressPool}
}
func (p *Packet) Marshal() ([]byte, error) {
@ -129,25 +129,26 @@ func ShouldDiscardInformationRequest(p *Packet, serverDuid []byte) error {
}
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())
bootFileUrl, err := b.Configuration.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, bootFileUrl), nil
in.Options.ClientArchType(), association.IpAddress, bootFileUrl, b.Configuration.GetPreference()), 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())
bootFileUrl, err := b.Configuration.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, bootFileUrl), nil
case MsgInformationRequest:
bootFileUrl, err := b.BootFileUrl.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType())
bootFileUrl, err := b.Configuration.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType())
if err != nil {
return nil, err
}
@ -161,8 +162,8 @@ func (b *PacketBuilder) BuildResponse(in *Packet) (*Packet, error) {
}
}
func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte,
bootFileUrl []byte) *Packet {
func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress,
bootFileUrl, preference []byte) *Packet {
ret_options := make(Options)
ret_options.AddOption(MakeOption(OptClientId, clientId))
ret_options.AddOption(MakeIaNaOption(iaId, b.calculateT1(), b.calculateT2(),
@ -172,15 +173,15 @@ func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId [
ret_options.AddOption(MakeOption(OptVendorClass, []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116})) // HTTPClient
}
ret_options.AddOption(MakeOption(OptBootfileUrl, bootFileUrl))
if preference != nil {ret_options.AddOption(MakeOption(OptPreference, preference))}
// ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1"))
//ret_options.AddOption(OptBootfileParam, []byte("http://")
//ret.Options[OptPreference] = [][]byte("http://")
return &Packet{Type: MsgAdvertise, TransactionID: transactionId, Options: ret_options}
}
func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress []byte,
func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress,
bootFileUrl []byte) *Packet {
ret_options := make(Options)
ret_options.AddOption(MakeOption(OptClientId, clientId))

View File

@ -16,7 +16,8 @@ func TestMakeMsgAdvertise(t *testing.T) {
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, expectedBootFileUrl)
msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x11, expectedIp,
expectedBootFileUrl, nil)
if msg.Type != MsgAdvertise {
t.Fatalf("Expected message type %d, got %d", MsgAdvertise, msg.Type)
@ -59,6 +60,28 @@ func TestMakeMsgAdvertise(t *testing.T) {
if iaNaOption == nil {
t.Fatalf("interface non-temporary association option should be present")
}
preferenceOption := msg.Options[OptPreference]
if preferenceOption != nil {
t.Fatalf("Preference option shouldn't be set")
}
}
func TestShouldSetPreferenceOptionWhenSpecified(t *testing.T) {
builder := MakePacketBuilder([]byte("serverid"), 90, 100, nil,
NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), net.ParseIP("2001:db8:f00f:cafe::1"), 100))
expectedPreference := []byte{128}
msg := builder.MakeMsgAdvertise([3]byte{'t', 'i', 'd'}, []byte("clientid"), []byte("1234"), 0x11,
net.ParseIP("2001:db8:f00f:cafe::1"), []byte("http://bootfileurl"), expectedPreference)
preferenceOption := msg.Options[OptPreference]
if preferenceOption == nil {
t.Fatalf("Preference option should be set")
}
if string(expectedPreference) != string(preferenceOption.Value) {
t.Fatalf("Expected preference value %d, got %d", expectedPreference, preferenceOption.Value)
}
}
func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) {
@ -71,7 +94,8 @@ func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) {
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, expectedBootFileUrl)
msg := builder.MakeMsgAdvertise(transactionId, expectedClientId, []byte("1234"), 0x10, expectedIp,
expectedBootFileUrl, nil)
vendorClassOption := msg.Options[OptVendorClass]
if vendorClassOption == nil {
@ -82,7 +106,7 @@ func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) {
t.Fatalf("Bootfile URL option should be present")
}
if string(expectedBootFileUrl) != string(bootfileUrlOption.Value) {
t.Fatalf("Expected bootfile URL %v, got %v", expectedBootFileUrl, bootfileUrlOption)
t.Fatalf("Expected bootfile URL %s, got %s", expectedBootFileUrl, bootfileUrlOption)
}
}

View File

@ -45,7 +45,11 @@ var bootIPv6Cmd = &cobra.Command{
}
s.Address = addr
s.BootUrls = dhcp6.MakeStaticBootConfiguration(httpBootUrl, ipxeUrl)
preference, err := cmd.Flags().GetUint8("preference")
if err != nil {
fatalf("Error reading flag: %s", err)
}
s.BootConfig = dhcp6.MakeStaticBootConfiguration(httpBootUrl, ipxeUrl, preference, cmd.Flags().Changed("preference"))
fmt.Println(s.Serve())
},
@ -56,6 +60,7 @@ func serverv6ConfigFlags(cmd *cobra.Command) {
cmd.Flags().StringP("ipxe-url", "", "", "IPXE config file url, e.g. http://[2001:db8:f00f:cafe::4]/script.ipxe")
cmd.Flags().StringP("httpboot-url", "", "", "HTTPBoot url, e.g. http://[2001:db8:f00f:cafe::4]/bootx64.efi")
cmd.Flags().Bool("debug", false, "Enable debug-level logging")
cmd.Flags().Uint8("preference", 255, "Set dhcp server preference value")
}
func init() {

View File

@ -42,7 +42,12 @@ var ipv6ApiCmd = &cobra.Command{
}
s.Address = addr
s.BootUrls = dhcp6.MakeApiBootConfiguration(apiUrl, apiTimeout)
preference, err := cmd.Flags().GetUint8("preference")
if err != nil {
fatalf("Error reading flag: %s", err)
}
s.BootConfig = dhcp6.MakeApiBootConfiguration(apiUrl, apiTimeout, preference, cmd.Flags().Changed("preference"))
fmt.Println(s.Serve())
},
@ -53,6 +58,7 @@ func serverv6ApiConfigFlags(cmd *cobra.Command) {
cmd.Flags().StringP("api-request-url", "", "", "Ipv6-specific API server url")
cmd.Flags().Duration("api-request-timeout", 5*time.Second, "Timeout for request to the API server")
cmd.Flags().Bool("debug", false, "Enable debug-level logging")
cmd.Flags().Uint8("preference", 255, "Set dhcp server preference value")
}
func init() {

View File

@ -21,11 +21,11 @@ func (s *ServerV6) serveDHCP(conn *dhcp6.Conn, packetBuilder *dhcp6.PacketBuilde
response, err := packetBuilder.BuildResponse(pkt)
if err != nil {
s.log("dhcpv6", fmt.Sprintf("Error creating response for transaction: %s: %s", pkt.TransactionID, err))
s.log("dhcpv6", fmt.Sprintf("Error creating response for transaction: %d: %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))
s.log("dhcpv6", fmt.Sprintf("Don't know how to respond to packet type: %d (transaction id %d)", pkt.Type, pkt.TransactionID))
continue
}

View File

@ -9,10 +9,10 @@ import (
)
type ServerV6 struct {
Address string
Port string
Duid []byte
BootUrls dhcp6.BootConfiguration
Address string
Port string
Duid []byte
BootConfig dhcp6.BootConfiguration
errs chan error
@ -47,7 +47,7 @@ func (s *ServerV6) Serve() error {
s.SetDUID(dhcp.SourceHardwareAddress())
addressPool := dhcp6.NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::10"), net.ParseIP("2001:db8:f00f:cafe::100"), 1850)
packetBuilder := dhcp6.MakePacketBuilder(s.Duid, 1800, 1850, s.BootUrls, addressPool)
packetBuilder := dhcp6.MakePacketBuilder(s.Duid, 1800, 1850, s.BootConfig, addressPool)
go func() { s.errs <- s.serveDHCP(dhcp, packetBuilder) }()