Added handling of multiple IANAs per solicit/request/release message

This commit is contained in:
Dmitri Dolguikh 2017-10-25 16:08:14 -07:00 committed by Dave Anderson
parent f89f6af9a6
commit 1cf3ef9b5b
4 changed files with 105 additions and 71 deletions

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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() {