netboot/dhcp6/options.go
2018-02-05 12:33:17 -08:00

292 lines
7.7 KiB
Go

package dhcp6
import (
"encoding/binary"
"fmt"
"bytes"
"net"
)
const (
OptClientId uint16 = 1 // IPMask
OptServerId = 2 // int32
OptIaNa = 3 // IPs
OptIaTa = 4 // IPs
OptIaAddr = 5 // string
OptOro = 6 // uint16
OptPreference = 7 // string
OptElapsedTime = 8 // IP
OptRelayMessage = 9 // IP
OptAuth = 11 // []byte
OptUnicast = 12 // IP
OptStatusCode = 13 // uint32
OptRapidCommit = 14 // byte
OptUserClass = 15 // IP
OptVendorClass = 16 // []byte
OptVendorOpts = 17 // string
OptInterfaceId = 18 // uint16
OptReconfMsg = 19 // uint32
OptReconfAccept = 20 // uint32
OptRecursiveDns = 23 // []byte
OptBootfileUrl = 59
OptBootfileParam = 60 //[][]byte
OptClientArchType = 61 //[][]byte, sent by the client
// 24? Domain search list
)
type Option struct {
Id uint16
Length uint16
Value []byte
}
func MakeOption(id uint16, value []byte) *Option {
return &Option{ Id: id, Length: uint16(len(value)), Value: value}
}
type Options map[uint16][]*Option
func MakeOptions(bs []byte) (Options, error) {
to_ret := make(Options)
for len(bs) > 0 {
o, err := UnmarshalOption(bs)
if err != nil {
return nil, err
}
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) {
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
}
func (o Options) HumanReadableIaNa(opt Option) []string {
to_ret := make([]string, 0)
to_ret = append(to_ret, fmt.Sprintf("Option: OptIaNa | len %d | iaid %x | t1 %d | t2 %d\n",
opt.Length, opt.Value[0:4], binary.BigEndian.Uint32(opt.Value[4:8]), binary.BigEndian.Uint32(opt.Value[8:12])))
if opt.Length <= 12 {
return to_ret // no options
}
iaOptions := opt.Value[12:]
for len(iaOptions) > 0 {
l := uint16(binary.BigEndian.Uint16(iaOptions[2:4]))
id := uint16(binary.BigEndian.Uint16(iaOptions[0:2]))
switch id {
case OptIaAddr:
ip := make(net.IP, 16)
copy(ip, iaOptions[4:20])
to_ret = append(to_ret, fmt.Sprintf("\tOption: IA_ADDR | len %d | ip %s | preferred %d | valid %d | %v \n",
l, ip, binary.BigEndian.Uint32(iaOptions[20:24]), binary.BigEndian.Uint32(iaOptions[24:28]), iaOptions[28:4+l]))
default:
to_ret = append(to_ret, fmt.Sprintf("\tOption: id %d | len %d | %s\n",
id, l, iaOptions[4:4+l]))
}
iaOptions = iaOptions[4+l:]
}
return to_ret
}
func (o Options) AddOption(option *Option) {
_, present := o[option.Id]; if !present {
o[option.Id] = make([]*Option, 0)
}
o[option.Id] = append(o[option.Id], option)
}
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:], serializedIaOption)
return MakeOption(OptIaNa, value)
}
func MakeIaAddrOption(addr net.IP, preferredLifetime, validLifetime uint32) *Option {
value := make([]byte, 24)
copy(value[0:], addr)
binary.BigEndian.PutUint32(value[16:], preferredLifetime)
binary.BigEndian.PutUint32(value[20:], validLifetime)
return MakeOption(OptIaAddr, value)
}
func MakeStatusOption(statusCode uint16, message string) *Option {
value := make([]byte, 2 + len(message))
binary.BigEndian.PutUint16(value[0:], statusCode)
copy(value[2:], []byte(message))
return MakeOption(OptStatusCode, value)
}
func MakeDNSServersOption(addresses []net.IP) *Option {
value := make([]byte, 16*len(addresses))
for i, dnsAddress := range addresses {
copy(value[i*16:], dnsAddress)
}
return MakeOption(OptRecursiveDns, value)
}
func (o Options) Marshal() ([]byte, error) {
buffer := bytes.NewBuffer(make([]byte, 0, 1446))
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
}
func (o *Option) Marshal() ([]byte, error) {
buffer := bytes.NewBuffer(make([]byte, 0, o.Length + 2))
err := binary.Write(buffer, binary.BigEndian, o.Id)
if err != nil {
return nil, fmt.Errorf("Error serializing option id: %s", err)
}
err = binary.Write(buffer, binary.BigEndian, o.Length)
if err != nil {
return nil, fmt.Errorf("Error serializing option length: %s", err)
}
err = binary.Write(buffer, binary.BigEndian, o.Value)
if err != nil {
return nil, fmt.Errorf("Error serializing option value: %s", err)
}
return buffer.Bytes(), nil
}
func (o Options) UnmarshalOptionRequestOption() map[uint16]bool {
to_ret := make(map[uint16]bool)
_, present := o[OptOro]; if !present {
return to_ret
}
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
}
func (o Options) HasBootFileUrlOption() bool {
requested_options := o.UnmarshalOptionRequestOption()
_, present := requested_options[OptBootfileUrl]
return present
}
func (o Options) HasClientId() bool {
_, present := o[OptClientId]
return present
}
func (o Options) HasServerId() bool {
_, present := o[OptServerId]
return present
}
func (o Options) HasIaNa() bool {
_, present := o[OptIaNa]
return present
}
func (o Options) HasIaTa() bool {
_, present := o[OptIaTa]
return present
}
func (o Options) HasClientArchType() bool {
_, present := o[OptClientArchType]
return present
}
func (o Options) ClientId() []byte {
opt, exists := o[OptClientId]
if exists {
return opt[0].Value
}
return nil
}
func (o Options) ServerId() []byte {
opt, exists := o[OptServerId]
if exists {
return opt[0].Value
}
return nil
}
func (o Options) IaNaIds() [][]byte {
options, exists := o[OptIaNa]
ret := make([][]byte, 0)
if exists {
for _, option := range(options) {
ret = append(ret, option.Value[0:4])
}
return ret
}
return ret
}
func (o Options) ClientArchType() uint16 {
opt, exists := o[OptClientArchType]
if exists {
return binary.BigEndian.Uint16(opt[0].Value)
}
return 0
}
func (o Options) BootfileUrl() []byte {
opt, exists := o[OptBootfileUrl]
if exists {
return opt[0].Value
}
return nil
}