diff --git a/dhcp6/options.go b/dhcp6/options.go index e7ae3dd..1ba7671 100644 --- a/dhcp6/options.go +++ b/dhcp6/options.go @@ -49,31 +49,36 @@ type Options map[uint16][]*Option func MakeOptions(bs []byte) (Options, error) { to_ret := make(Options) for len(bs) > 0 { - optionLength := uint16(binary.BigEndian.Uint16(bs[2:4])) - optionId := uint16(binary.BigEndian.Uint16(bs[0:2])) - switch optionId { - // parse client_id - // parse server_id - //parse ipaddr - case OptOro: - if optionLength% 2 != 0 { - return nil, fmt.Errorf("OptionID request for options (6) length should be even number of bytes: %d", optionLength) - } - default: - if len(bs[4:]) < int(optionLength) { - fmt.Printf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) - return nil, fmt.Errorf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) - } + o, err := UnmarshalOption(bs) + if err != nil { + return nil, err } - _, present := to_ret[optionId]; if !present { - to_ret[optionId] = make([]*Option, 1) - } - to_ret[optionId] = append(to_ret[optionId], &Option{ Id: optionId, Length: optionLength, Value: bs[4 : 4+optionLength]}) - bs = bs[4+optionLength:] + to_ret[o.Id] = append(to_ret[o.Id], &Option{ Id: o.Id, Length: o.Length, Value: bs[4 : 4+o.Length]}) + bs = bs[4+o.Length:] } return to_ret, nil } +func UnmarshalOption(bs []byte) (*Option, error) { + optionLength := uint16(binary.BigEndian.Uint16(bs[2:4])) + optionId := uint16(binary.BigEndian.Uint16(bs[0:2])) + switch optionId { + // parse client_id + // parse server_id + //parse ipaddr + case OptOro: + if optionLength% 2 != 0 { + return nil, fmt.Errorf("OptionID request for options (6) length should be even number of bytes: %d", optionLength) + } + default: + if len(bs[4:]) < int(optionLength) { + fmt.Printf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) + return nil, fmt.Errorf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) + } + } + return &Option{ Id: optionId, Length: optionLength, Value: bs[4 : 4+optionLength]}, nil +} + func (o Options) HumanReadable() []string { to_ret := make([]string, 0, len(o)) for _, multipleOptions := range(o) { @@ -128,13 +133,13 @@ func (o Options) AddOption(option *Option) { o[option.Id] = append(o[option.Id], option) } -func MakeIaNaOption(iaid []byte, t1, t2 uint32, iaAddr *Option) *Option { - serializedIaAddr, _ := iaAddr.Marshal() - value := make([]byte, 12 + len(serializedIaAddr)) +func MakeIaNaOption(iaid []byte, t1, t2 uint32, iaOption *Option) *Option { + serializedIaOption, _ := iaOption.Marshal() + value := make([]byte, 12 + len(serializedIaOption)) copy(value[0:], iaid[0:4]) binary.BigEndian.PutUint32(value[4:], t1) binary.BigEndian.PutUint32(value[8:], t2) - copy(value[12:], serializedIaAddr) + copy(value[12:], serializedIaOption) return MakeOption(OptIaNa, value) } @@ -201,7 +206,7 @@ func (o Options) UnmarshalOptionRequestOption() map[uint16]bool { return to_ret } -func (o Options) RequestedBootFileUrlOption() bool { +func (o Options) HasBootFileUrlOption() bool { requested_options := o.UnmarshalOptionRequestOption() _, present := requested_options[OptBootfileUrl] return present diff --git a/dhcp6/packet.go b/dhcp6/packet.go index 0cb34be..39f582f 100644 --- a/dhcp6/packet.go +++ b/dhcp6/packet.go @@ -86,7 +86,7 @@ func (p *Packet) ShouldDiscard(serverDuid []byte) error { func ShouldDiscardSolicit(p *Packet) error { options := p.Options - if !options.RequestedBootFileUrlOption() { + if !options.HasBootFileUrlOption() { return fmt.Errorf("'Solicit' packet doesn't have file url option") } if !options.HasClientId() { @@ -100,7 +100,7 @@ func ShouldDiscardSolicit(p *Packet) error { func ShouldDiscardRequest(p *Packet, serverDuid []byte) error { options := p.Options - if !options.RequestedBootFileUrlOption() { + if !options.HasBootFileUrlOption() { return fmt.Errorf("'Request' packet doesn't have file url option") } if !options.HasClientId() { @@ -117,7 +117,7 @@ func ShouldDiscardRequest(p *Packet, serverDuid []byte) error { func ShouldDiscardInformationRequest(p *Packet, serverDuid []byte) error { options := p.Options - if !options.RequestedBootFileUrlOption() { + if !options.HasBootFileUrlOption() { return fmt.Errorf("'Information-request' packet doesn't have boot file url option") } if options.HasIaNa() || options.HasIaTa() { diff --git a/dhcp6/packet_test.go b/dhcp6/packet_test.go index 3f77735..48a91df 100644 --- a/dhcp6/packet_test.go +++ b/dhcp6/packet_test.go @@ -10,7 +10,7 @@ import ( func TestMakeMsgAdvertise(t *testing.T) { expectedClientId := []byte("clientid") expectedServerId := []byte("serverid") - expectedInterfaceId := []byte("interfaceid") + expectedInterfaceId := []byte("id-1") transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") expectedBootFileUrl := []byte("http://bootfileurl") @@ -65,7 +65,7 @@ func TestMakeMsgAdvertise(t *testing.T) { } func TestShouldSetPreferenceOptionWhenSpecified(t *testing.T) { - identityAssociation := &IdentityAssociation{IpAddress: net.ParseIP("2001:db8:f00f:cafe::1"), InterfaceId: []byte("interfaceid")} + identityAssociation := &IdentityAssociation{IpAddress: net.ParseIP("2001:db8:f00f:cafe::1"), InterfaceId: []byte("id-1")} builder := MakePacketBuilder([]byte("serverid"), 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), 1, 100)) @@ -89,7 +89,7 @@ func TestMakeMsgAdvertiseWithHttpClientArch(t *testing.T) { transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") expectedBootFileUrl := []byte("http://bootfileurl") - identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("interfaceid")} + identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("id-1")} builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), 1, 100)) @@ -162,7 +162,7 @@ func TestMakeMsgReply(t *testing.T) { transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") expectedBootFileUrl := []byte("http://bootfileurl") - identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("interfaceid")} + identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("id-1")} builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), 1, 100)) @@ -219,7 +219,7 @@ func TestMakeMsgReplyWithHttpClientArch(t *testing.T) { transactionId := [3]byte{'1', '2', '3'} expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") expectedBootFileUrl := []byte("http://bootfileurl") - identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("interfaceid")} + identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("id-1")} builder := MakePacketBuilder(expectedServerId, 90, 100, nil, NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), 1, 100)) @@ -241,6 +241,61 @@ func TestMakeMsgReplyWithHttpClientArch(t *testing.T) { } } +func TestMakeMsgReplyWithNoAddrsAvailable(t *testing.T) { + expectedClientId := []byte("clientid") + expectedServerId := []byte("serverid") + transactionId := [3]byte{'1', '2', '3'} + expectedIp := net.ParseIP("2001:db8:f00f:cafe::1") + expectedBootFileUrl := []byte("http://bootfileurl") + identityAssociation := &IdentityAssociation{IpAddress: expectedIp, InterfaceId: []byte("id-1")} + expectedErrorMessage := "Boom!" + + builder := MakePacketBuilder(expectedServerId, 90, 100, nil, + NewRandomAddressPool(net.ParseIP("2001:db8:f00f:cafe::1"), 1, 100)) + + msg := builder.MakeMsgReplyWithNoAddrsAvailable(transactionId, expectedClientId, 0x10, + []*IdentityAssociation{identityAssociation}, [][]byte{[]byte("id-2")}, expectedBootFileUrl, + fmt.Errorf(expectedErrorMessage)) + + iaNaOption := msg.Options[OptIaNa] + if iaNaOption == nil { + t.Fatalf("interface non-temporary association options should be present") + } + if (len(iaNaOption)) != 2 { + t.Fatalf("Expected 2 identity associations, got %d", len(iaNaOption)) + } + var okIaNaOption, failedIaNaOption []byte + if string(iaNaOption[0].Value[0:4]) == string("id-1") { + okIaNaOption = iaNaOption[0].Value + failedIaNaOption = iaNaOption[1].Value + } else { + okIaNaOption = iaNaOption[1].Value + failedIaNaOption = iaNaOption[0].Value + } + + possiblyIaAddrOption, err := UnmarshalOption(okIaNaOption[12:]) + if err != nil { + t.Fatalf("Failed to unmarshal IaNa options: %s", err) + } + if possiblyIaAddrOption.Id != OptIaAddr { + t.Fatalf("Expected option 5 (ia address), got %d", possiblyIaAddrOption.Id) + } + + possiblyStatusOption, err := UnmarshalOption(failedIaNaOption[12:]) + if err != nil { + t.Fatalf("Failed to unmarshal IaNa options: %s", err) + } + if possiblyStatusOption.Id != OptStatusCode { + t.Fatalf("Expected option 13 (status code), got %d", possiblyStatusOption.Id) + } + if binary.BigEndian.Uint16(possiblyStatusOption.Value[0:2]) != uint16(2) { + t.Fatalf("Expected status code 2, got %d", binary.BigEndian.Uint16(possiblyStatusOption.Value[0:2])) + } + if string(possiblyStatusOption.Value[2:]) != expectedErrorMessage { + t.Fatalf("Expected message %s, got %s", expectedErrorMessage, string(possiblyStatusOption.Value[2:])) + } +} + func TestMakeMsgInformationRequestReply(t *testing.T) { expectedClientId := []byte("clientid") expectedServerId := []byte("serverid")