From 1cf3ef9b5bea9a21d96378fb38bf6775911f885f Mon Sep 17 00:00:00 2001 From: Dmitri Dolguikh Date: Wed, 25 Oct 2017 16:08:14 -0700 Subject: [PATCH] Added handling of multiple IANAs per solicit/request/release message --- dhcp6/address_pool.go | 4 +-- dhcp6/options.go | 69 +++++++++++++++++++++++------------- dhcp6/packet.go | 40 +++++++++++---------- dhcp6/random_address_pool.go | 63 ++++++++++++++++++-------------- 4 files changed, 105 insertions(+), 71 deletions(-) diff --git a/dhcp6/address_pool.go b/dhcp6/address_pool.go index 281e4c8..740245c 100644 --- a/dhcp6/address_pool.go +++ b/dhcp6/address_pool.go @@ -13,6 +13,6 @@ type IdentityAssociation struct { } type AddressPool interface { - ReserveAddress(clientId, interfaceId []byte) *IdentityAssociation - ReleaseAddress(clientId, interfaceId []byte) + ReserveAddresses(clientId []byte, interfaceIds [][]byte) []*IdentityAssociation + ReleaseAddresses(clientId []byte, interfaceIds [][]byte) } diff --git a/dhcp6/options.go b/dhcp6/options.go index eecaf46..7734a65 100644 --- a/dhcp6/options.go +++ b/dhcp6/options.go @@ -44,7 +44,7 @@ func MakeOption(id uint16, value []byte) *Option { return &Option{ Id: id, Length: uint16(len(value)), Value: value} } -type Options map[uint16]*Option +type Options map[uint16][]*Option func MakeOptions(bs []byte) (Options, error) { to_ret := make(Options) @@ -54,7 +54,6 @@ func MakeOptions(bs []byte) (Options, error) { switch optionId { // parse client_id // parse server_id - // parse IaNa # do I need to support IaTa? //parse ipaddr case OptOro: if optionLength% 2 != 0 { @@ -66,7 +65,10 @@ func MakeOptions(bs []byte) (Options, error) { return nil, fmt.Errorf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) } } - to_ret[optionId] = &Option{ Id: optionId, Length: optionLength, Value: bs[4 : 4+optionLength]} + _, 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:] } return to_ret, nil @@ -74,12 +76,14 @@ func MakeOptions(bs []byte) (Options, error) { func (o Options) HumanReadable() []string { to_ret := make([]string, 0, len(o)) - for _, opt := range(o) { - switch opt.Id { - case 3: - to_ret = append(to_ret, o.HumanReadableIaNa(*opt)...) - default: - to_ret = append(to_ret, fmt.Sprintf("Option: %d | %d | %d | %s\n", opt.Id, opt.Length, opt.Value, opt.Value)) + for _, multipleOptions := range(o) { + for _, option := range(multipleOptions) { + switch option.Id { + case 3: + to_ret = append(to_ret, o.HumanReadableIaNa(*option)...) + default: + to_ret = append(to_ret, fmt.Sprintf("Option: %d | %d | %d | %s\n", option.Id, option.Length, option.Value, option.Value)) + } } } return to_ret @@ -118,7 +122,10 @@ func (o Options) HumanReadableIaNa(opt Option) []string { } func (o Options) AddOption(option *Option) { - o[option.Id] = option + _, present := o[option.Id]; if !present { + o[option.Id] = make([]*Option, 1) + } + o[option.Id] = append(o[option.Id], option) } func MakeIaNaOption(iaid []byte, t1, t2 uint32, iaAddr *Option) (*Option) { @@ -141,13 +148,15 @@ func MakeIaAddrOption(addr net.IP, preferredLifetime, validLifetime uint32) (*Op func (o Options) Marshal() ([]byte, error) { buffer := bytes.NewBuffer(make([]byte, 0, 1446)) - for _, v := range(o) { - serialized, err := v.Marshal() - if err != nil { - return nil, fmt.Errorf("Error serializing option value: %s", err) - } - if err := binary.Write(buffer, binary.BigEndian, serialized); err != nil { - return nil, fmt.Errorf("Error serializing option value: %s", err) + for _, multipleOptions := range(o) { + for _, o := range (multipleOptions) { + serialized, err := o.Marshal() + if err != nil { + return nil, fmt.Errorf("Error serializing option value: %s", err) + } + if err := binary.Write(buffer, binary.BigEndian, serialized); err != nil { + return nil, fmt.Errorf("Error serializing option value: %s", err) + } } } return buffer.Bytes(), nil @@ -178,8 +187,8 @@ func (o Options) UnmarshalOptionRequestOption() map[uint16]bool { return to_ret } - oro_content := o[OptOro].Value - for i := 0; i < int(o[OptOro].Length)/2; i++ { + oro_content := o[OptOro][0].Value + for i := 0; i < int(o[OptOro][0].Length)/2; i++ { to_ret[uint16(binary.BigEndian.Uint16(oro_content[i*2:(i+1)*2]))] = true } return to_ret @@ -219,15 +228,27 @@ func (o Options) HasClientArchType() bool { func (o Options) ClientId() []byte { opt, exists := o[OptClientId] if exists { - return opt.Value + return opt[0].Value } return nil } -func (o Options) IaNaId() []byte { - opt, exists := o[OptIaNa] +func (o Options) ServerId() []byte { + opt, exists := o[OptServerId] if exists { - return opt.Value[0:4] + return opt[0].Value + } + return nil +} + +func (o Options) IaNaIds() [][]byte { + options, exists := o[OptIaNa] + if exists { + ret := make([][]byte, len(options)) + for _, option := range(options) { + ret = append(ret, option.Value[0:4]) + } + return ret } return nil } @@ -235,7 +256,7 @@ func (o Options) IaNaId() []byte { func (o Options) ClientArchType() uint16 { opt, exists := o[OptClientArchType] if exists { - return binary.BigEndian.Uint16(opt.Value) + return binary.BigEndian.Uint16(opt[0].Value) } return 0 } diff --git a/dhcp6/packet.go b/dhcp6/packet.go index 0af6580..31dcd2d 100644 --- a/dhcp6/packet.go +++ b/dhcp6/packet.go @@ -108,7 +108,7 @@ func ShouldDiscardRequest(p *Packet, serverDuid []byte) error { if !options.HasServerId() { return fmt.Errorf("'Request' packet has no server id option") } - if bytes.Compare(options[OptServerId].Value, serverDuid) != 0 { + if bytes.Compare(options.ServerId(), serverDuid) != 0 { return fmt.Errorf("'Request' packet's server id option (%d) is different from ours (%d)", options[OptServerId].Value, serverDuid) } return nil @@ -122,7 +122,7 @@ func ShouldDiscardInformationRequest(p *Packet, serverDuid []byte) error { if options.HasIaNa() || options.HasIaTa() { return fmt.Errorf("'Information-request' packet has an IA option present") } - if options.HasServerId() && (bytes.Compare(options[OptServerId].Value, serverDuid) != 0) { + if options.HasServerId() && (bytes.Compare(options.ServerId(), serverDuid) != 0) { return fmt.Errorf("'Information-request' packet's server id option (%d) is different from ours (%d)", options[OptServerId].Value, serverDuid) } return nil @@ -132,21 +132,21 @@ 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.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, b.Configuration.GetPreference()), nil + associations := b.Addresses.ReserveAddresses(in.Options.ClientId(), in.Options.IaNaIds()) + return b.MakeMsgAdvertise(in.TransactionID, in.Options.ClientId(), + in.Options.ClientArchType(), associations, bootFileUrl, b.Configuration.GetPreference()), nil case MsgRequest: - association := b.Addresses.ReserveAddress(in.Options.ClientId(), in.Options.IaNaId()) 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 + associations := b.Addresses.ReserveAddresses(in.Options.ClientId(), in.Options.IaNaIds()) + return b.MakeMsgReply(in.TransactionID, in.Options.ClientId(), + in.Options.ClientArchType(), associations, bootFileUrl), nil case MsgInformationRequest: bootFileUrl, err := b.Configuration.GetBootUrl(b.ExtractLLAddressOrId(in.Options.ClientId()), in.Options.ClientArchType()) if err != nil { @@ -155,19 +155,21 @@ func (b *PacketBuilder) BuildResponse(in *Packet) (*Packet, error) { return b.MakeMsgInformationRequestReply(in.TransactionID, in.Options.ClientId(), in.Options.ClientArchType(), bootFileUrl), nil case MsgRelease: - b.Addresses.ReleaseAddress(in.Options.ClientId(), in.Options.IaNaId()) + b.Addresses.ReleaseAddresses(in.Options.ClientId(), in.Options.IaNaIds()) return b.MakeMsgReleaseReply(in.TransactionID, in.Options.ClientId()), nil default: return nil, nil } } -func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress, - bootFileUrl, preference []byte) *Packet { +func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId []byte, clientArchType uint16, + associations []*IdentityAssociation, bootFileUrl, preference []byte) *Packet { ret_options := make(Options) ret_options.AddOption(MakeOption(OptClientId, clientId)) - ret_options.AddOption(MakeIaNaOption(iaId, b.calculateT1(), b.calculateT2(), - MakeIaAddrOption(ipAddress, b.PreferredLifetime, b.ValidLifetime))) + for _, association := range(associations) { + ret_options.AddOption(MakeIaNaOption(association.InterfaceId, b.calculateT1(), b.calculateT2(), + MakeIaAddrOption(association.IpAddress, b.PreferredLifetime, b.ValidLifetime))) + } 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 @@ -175,18 +177,20 @@ func (b *PacketBuilder) MakeMsgAdvertise(transactionId [3]byte, clientId, iaId [ 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(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1")) //ret_options.AddOption(OptBootfileParam, []byte("http://") return &Packet{Type: MsgAdvertise, TransactionID: transactionId, Options: ret_options} } -func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId, iaId []byte, clientArchType uint16, ipAddress, - bootFileUrl []byte) *Packet { +func (b *PacketBuilder) MakeMsgReply(transactionId [3]byte, clientId []byte, clientArchType uint16, + associations []*IdentityAssociation, bootFileUrl []byte) *Packet { ret_options := make(Options) ret_options.AddOption(MakeOption(OptClientId, clientId)) - ret_options.AddOption(MakeIaNaOption(iaId, b.calculateT1(), b.calculateT2(), - MakeIaAddrOption(ipAddress, b.PreferredLifetime, b.ValidLifetime))) + for _, association := range(associations) { + ret_options.AddOption(MakeIaNaOption(association.InterfaceId, b.calculateT1(), b.calculateT2(), + MakeIaAddrOption(association.IpAddress, b.PreferredLifetime, b.ValidLifetime))) + } 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 diff --git a/dhcp6/random_address_pool.go b/dhcp6/random_address_pool.go index 60538dc..41bd8aa 100644 --- a/dhcp6/random_address_pool.go +++ b/dhcp6/random_address_pool.go @@ -75,45 +75,54 @@ func NewRandomAddressPool(poolStartAddress, poolEndAddress net.IP, validLifetime return to_ret } -func (p *RandomAddressPool) ReserveAddress(clientId, interfaceId []byte) *IdentityAssociation { +func (p *RandomAddressPool) ReserveAddresses(clientId []byte, interfaceIds [][]byte) []*IdentityAssociation { p.lock.Lock() defer p.lock.Unlock() - clientIdHash := p.calculateIaIdHash(clientId, interfaceId) - association, exists := p.identityAssociations[clientIdHash]; if exists { - return association - } + ret := make([]*IdentityAssociation, len(interfaceIds)) - for { - rng := rand.New(rand.NewSource(p.timeNow().UnixNano())) - // we assume that ip addresses adhere to high 64 bits for net and subnet ids, low 64 bits are for host id rule - hostOffset := rng.Uint64()%(p.poolEndAddress.Uint64() - p.poolStartAddress.Uint64() + 1) - newIp := big.NewInt(0).Add(p.poolStartAddress, big.NewInt(0).SetUint64(hostOffset)) - _, exists := p.usedIps[newIp.Uint64()]; if !exists { - timeNow := p.timeNow() - to_ret := &IdentityAssociation{ClientId: clientId, - InterfaceId: interfaceId, - IpAddress: newIp.Bytes(), - CreatedAt: timeNow } - p.identityAssociations[clientIdHash] = to_ret - p.usedIps[newIp.Uint64()] = struct{}{} - p.identityAssociationExpirations.Push(&AssociationExpiration{expiresAt: p.calculateAssociationExpiration(timeNow), ia: to_ret}) - return to_ret + for _, interfaceId := range (interfaceIds) { + clientIdHash := p.calculateIaIdHash(clientId, interfaceId) + association, exists := p.identityAssociations[clientIdHash]; + if exists { + ret = append(ret, association) + continue + } + + for { + rng := rand.New(rand.NewSource(p.timeNow().UnixNano())) + // we assume that ip addresses adhere to high 64 bits for net and subnet ids, low 64 bits are for host id rule + hostOffset := rng.Uint64() % (p.poolEndAddress.Uint64() - p.poolStartAddress.Uint64() + 1) + newIp := big.NewInt(0).Add(p.poolStartAddress, big.NewInt(0).SetUint64(hostOffset)) + _, exists := p.usedIps[newIp.Uint64()]; + if !exists { + timeNow := p.timeNow() + association := &IdentityAssociation{ClientId: clientId, + InterfaceId: interfaceId, + IpAddress: newIp.Bytes(), + CreatedAt: timeNow} + p.identityAssociations[clientIdHash] = association + p.usedIps[newIp.Uint64()] = struct{}{} + p.identityAssociationExpirations.Push(&AssociationExpiration{expiresAt: p.calculateAssociationExpiration(timeNow), ia: association}) + ret = append(ret, association) + } } } - return nil + return ret } -func (p *RandomAddressPool) ReleaseAddress(clientId, interfaceId []byte) { +func (p *RandomAddressPool) ReleaseAddresses(clientId []byte, interfaceIds [][]byte) { p.lock.Lock() defer p.lock.Unlock() - association, exists := p.identityAssociations[p.calculateIaIdHash(clientId, interfaceId)] - if !exists { - return + for _, interfaceId := range(interfaceIds) { + association, exists := p.identityAssociations[p.calculateIaIdHash(clientId, interfaceId)] + if !exists { + continue + } + delete(p.usedIps, big.NewInt(0).SetBytes(association.IpAddress).Uint64()) + delete(p.identityAssociations, p.calculateIaIdHash(clientId, interfaceId)) } - delete(p.usedIps, big.NewInt(0).SetBytes(association.IpAddress).Uint64()) - delete(p.identityAssociations, p.calculateIaIdHash(clientId, interfaceId)) } func (p *RandomAddressPool) ExpireIdentityAssociations() {