mirror of
https://github.com/danderson/netboot.git
synced 2025-10-16 10:01:20 +02:00
Added handling of multiple IANAs per solicit/request/release message
This commit is contained in:
parent
f89f6af9a6
commit
1cf3ef9b5b
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user