diff options
author | Andrea Barberio <insomniac@slackware.it> | 2017-11-29 21:25:48 +0000 |
---|---|---|
committer | Andrea Barberio <insomniac@slackware.it> | 2017-12-05 23:32:27 +0000 |
commit | 1403bbe04ce275148b601c32e9551b2281110347 (patch) | |
tree | 6eacd224c2ea6b6e3865cb774970726809363d46 /dhcpv6/options |
Initial commit
Diffstat (limited to 'dhcpv6/options')
-rw-r--r-- | dhcpv6/options/clientid.go | 57 | ||||
-rw-r--r-- | dhcpv6/options/dnsrecursivenameserver.go | 59 | ||||
-rw-r--r-- | dhcpv6/options/domainsearchlist.go | 57 | ||||
-rw-r--r-- | dhcpv6/options/duid.go | 100 | ||||
-rw-r--r-- | dhcpv6/options/elapsedtime.go | 52 | ||||
-rw-r--r-- | dhcpv6/options/elapsedtime_test.go | 44 | ||||
-rw-r--r-- | dhcpv6/options/iaaddress.go | 86 | ||||
-rw-r--r-- | dhcpv6/options/iaprefix.go | 98 | ||||
-rw-r--r-- | dhcpv6/options/nontemporaryaddress.go | 86 | ||||
-rw-r--r-- | dhcpv6/options/options.go | 125 | ||||
-rw-r--r-- | dhcpv6/options/prefixdelegation.go | 86 | ||||
-rw-r--r-- | dhcpv6/options/requestedoption.go | 72 | ||||
-rw-r--r-- | dhcpv6/options/rfc1035label.go | 54 | ||||
-rw-r--r-- | dhcpv6/options/rfc1035label_test.go | 66 | ||||
-rw-r--r-- | dhcpv6/options/serverid.go | 57 | ||||
-rw-r--r-- | dhcpv6/options/statuscode.go | 63 | ||||
-rw-r--r-- | dhcpv6/options/types.go | 152 |
17 files changed, 1314 insertions, 0 deletions
diff --git a/dhcpv6/options/clientid.go b/dhcpv6/options/clientid.go new file mode 100644 index 0000000..71422d7 --- /dev/null +++ b/dhcpv6/options/clientid.go @@ -0,0 +1,57 @@ +package options + +// This module defines the OptClientId and DUID structures. +// https://www.ietf.org/rfc/rfc3315.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptClientId struct { + cid Duid +} + +func (op *OptClientId) Code() OptionCode { + return OPTION_CLIENTID +} + +func (op *OptClientId) ToBytes() []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_CLIENTID)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + buf = append(buf, op.cid.ToBytes()...) + return buf +} + +func (op *OptClientId) ClientID() Duid { + return op.cid +} + +func (op *OptClientId) SetClientID(cid Duid) { + op.cid = cid +} + +func (op *OptClientId) Length() int { + return op.cid.Length() +} + +func (op *OptClientId) String() string { + return fmt.Sprintf("OptClientId{cid=%v}", op.cid.String()) +} + +// build an OptClientId structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptClientId(data []byte) (*OptClientId, error) { + if len(data) < 2 { + // at least the DUID type is necessary to continue + return nil, fmt.Errorf("Invalid OptClientId data: shorter than 2 bytes") + } + opt := OptClientId{} + cid, err := DuidFromBytes(data) + if err != nil { + return nil, err + } + opt.cid = *cid + return &opt, nil +} diff --git a/dhcpv6/options/dnsrecursivenameserver.go b/dhcpv6/options/dnsrecursivenameserver.go new file mode 100644 index 0000000..a28fd1d --- /dev/null +++ b/dhcpv6/options/dnsrecursivenameserver.go @@ -0,0 +1,59 @@ +package options + +// This module defines the OptDNSRecursiveNameServer structure. +// https://www.ietf.org/rfc/rfc3646.txt + +import ( + "encoding/binary" + "fmt" + "net" +) + +type OptDNSRecursiveNameServer struct { + nameServers []net.IP +} + +func (op *OptDNSRecursiveNameServer) Code() OptionCode { + return DNS_RECURSIVE_NAME_SERVER +} + +func (op *OptDNSRecursiveNameServer) ToBytes() []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(DNS_RECURSIVE_NAME_SERVER)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + for _, ns := range op.nameServers { + buf = append(buf, ns...) + } + return buf +} + +func (op *OptDNSRecursiveNameServer) NameServers() []net.IP { + return op.nameServers +} + +func (op *OptDNSRecursiveNameServer) SetNameServers(ns []net.IP) { + op.nameServers = ns +} + +func (op *OptDNSRecursiveNameServer) Length() int { + return len(op.nameServers) * net.IPv6len +} + +func (op *OptDNSRecursiveNameServer) String() string { + return fmt.Sprintf("OptDNSRecursiveNameServer{nameservers=%v}", op.nameServers) +} + +// build an OptDNSRecursiveNameServer structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptDNSRecursiveNameServer(data []byte) (*OptDNSRecursiveNameServer, error) { + if len(data)%2 != 0 { + return nil, fmt.Errorf("Invalid OptDNSRecursiveNameServer data: length is not a multiple of 2") + } + opt := OptDNSRecursiveNameServer{} + var nameServers []net.IP + for i := 0; i < len(data); i += net.IPv6len { + nameServers = append(nameServers, data[i:i+net.IPv6len]) + } + opt.nameServers = nameServers + return &opt, nil +} diff --git a/dhcpv6/options/domainsearchlist.go b/dhcpv6/options/domainsearchlist.go new file mode 100644 index 0000000..048384f --- /dev/null +++ b/dhcpv6/options/domainsearchlist.go @@ -0,0 +1,57 @@ +package options + +// This module defines the OptDomainSearchList structure. +// https://www.ietf.org/rfc/rfc3646.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptDomainSearchList struct { + domainSearchList []string +} + +func (op *OptDomainSearchList) Code() OptionCode { + return DOMAIN_SEARCH_LIST +} + +func (op *OptDomainSearchList) ToBytes() []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(DOMAIN_SEARCH_LIST)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + buf = append(buf, LabelsToBytes(op.domainSearchList)...) + return buf +} + +func (op *OptDomainSearchList) DomainSearchList() []string { + return op.domainSearchList +} + +func (op *OptDomainSearchList) SetDomainSearchList(dsList []string) { + op.domainSearchList = dsList +} + +func (op *OptDomainSearchList) Length() int { + var length int + for _, label := range op.domainSearchList { + length += len(label) + 2 // add the first and the last length bytes + } + return length +} + +func (op *OptDomainSearchList) String() string { + return fmt.Sprintf("OptDomainSearchList{searchlist=%v}", op.domainSearchList) +} + +// build an OptDomainSearchList structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptDomainSearchList(data []byte) (*OptDomainSearchList, error) { + opt := OptDomainSearchList{} + var err error + opt.domainSearchList, err = LabelsFromBytes(data) + if err != nil { + return nil, err + } + return &opt, nil +} diff --git a/dhcpv6/options/duid.go b/dhcpv6/options/duid.go new file mode 100644 index 0000000..0fc3e33 --- /dev/null +++ b/dhcpv6/options/duid.go @@ -0,0 +1,100 @@ +package options + +import ( + "encoding/binary" + "fmt" + "github.com/insomniacslk/dhcp/iana" +) + +type DuidType uint16 + +const ( + DUID_LL DuidType = iota + DUID_LLT + DUID_EN +) + +var DuidTypeToString = map[DuidType]string{ + DUID_LL: "DUID-LL", + DUID_LLT: "DUID-LLT", + DUID_EN: "DUID-EN", +} + +type Duid struct { + Type DuidType + HwType iana.HwTypeType // for DUID-LLT and DUID-LL. Ignored otherwise. RFC 826 + Time uint32 // for DUID-LLT. Ignored otherwise + LinkLayerAddr []byte + EnterpriseNumber uint32 // for DUID-EN. Ignored otherwise + EnterpriseIdentifier []byte // for DUID-EN. Ignored otherwise +} + +func (d *Duid) Length() int { + if d.Type == DUID_LLT || d.Type == DUID_LL { + return 8 + len(d.LinkLayerAddr) + } + if d.Type == DUID_EN { + return 6 + len(d.EnterpriseIdentifier) + } + return 0 // should never happen +} + +func (d *Duid) ToBytes() []byte { + if d.Type == DUID_LLT || d.Type == DUID_LL { + buf := make([]byte, 8) + binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) + binary.BigEndian.PutUint16(buf[2:4], uint16(d.HwType)) + binary.BigEndian.PutUint32(buf[4:8], d.Time) + return append(buf, d.LinkLayerAddr...) + } else if d.Type == DUID_EN { + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], uint16(d.Type)) + binary.BigEndian.PutUint32(buf[2:6], d.EnterpriseNumber) + return append(buf, d.EnterpriseIdentifier...) + } + return []byte{} // should never happen +} + +func (d *Duid) String() string { + dtype := DuidTypeToString[d.Type] + if dtype == "" { + dtype = "Unknown" + } + hwtype := iana.HwTypeToString[d.HwType] + if hwtype == "" { + hwtype = "Unknown" + } + var hwaddr string + if d.HwType == iana.HwTypeEthernet { + for _, b := range d.LinkLayerAddr { + hwaddr += fmt.Sprintf("%02x:", b) + } + if len(hwaddr) > 0 && hwaddr[len(hwaddr)-1] == ':' { + hwaddr = hwaddr[:len(hwaddr)-1] + } + } + return fmt.Sprintf("DUID{type=%v hwtype=%v hwaddr=%v}", dtype, hwtype, hwaddr) +} + +func DuidFromBytes(data []byte) (*Duid, error) { + if len(data) < 2 { + return nil, fmt.Errorf("Invalid DUID: shorter than 2 bytes") + } + d := Duid{} + d.Type = DuidType(binary.BigEndian.Uint16(data[0:2])) + if d.Type == DUID_LLT || d.Type == DUID_LL { + if len(data) < 8 { + return nil, fmt.Errorf("Invalid DUID-LL/LLT: shorter than 8 bytes") + } + d.HwType = iana.HwTypeType(binary.BigEndian.Uint16(data[2:4])) + d.Time = binary.BigEndian.Uint32(data[4:8]) + d.LinkLayerAddr = data[8:] + } else if d.Type == DUID_EN { + if len(data) < 6 { + return nil, fmt.Errorf("Invalid DUID-EN: shorter than 6 bytes") + } + d.EnterpriseNumber = binary.BigEndian.Uint32(data[2:6]) + d.EnterpriseIdentifier = data[6:] + } + return &d, nil +} diff --git a/dhcpv6/options/elapsedtime.go b/dhcpv6/options/elapsedtime.go new file mode 100644 index 0000000..e95185d --- /dev/null +++ b/dhcpv6/options/elapsedtime.go @@ -0,0 +1,52 @@ +package options + +// This module defines the OptElapsedTime structure. +// https://www.ietf.org/rfc/rfc3315.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptElapsedTime struct { + elapsedTime uint16 +} + +func (op *OptElapsedTime) Code() OptionCode { + return OPTION_ELAPSED_TIME +} + +func (op *OptElapsedTime) ToBytes() []byte { + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_ELAPSED_TIME)) + binary.BigEndian.PutUint16(buf[2:4], 2) + binary.BigEndian.PutUint16(buf[4:6], uint16(op.elapsedTime)) + return buf +} + +func (op *OptElapsedTime) ElapsedTime() uint16 { + return op.elapsedTime +} + +func (op *OptElapsedTime) SetElapsedTime(elapsedTime uint16) { + op.elapsedTime = elapsedTime +} + +func (op *OptElapsedTime) Length() int { + return 2 +} + +func (op *OptElapsedTime) String() string { + return fmt.Sprintf("OptElapsedTime{elapsedtime=%v}", op.elapsedTime) +} + +// build an OptElapsedTime structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptElapsedTime(data []byte) (*OptElapsedTime, error) { + opt := OptElapsedTime{} + if len(data) != 2 { + return nil, fmt.Errorf("Invalid elapsed time data length. Expected 2 bytes, got %v", len(data)) + } + opt.elapsedTime = binary.BigEndian.Uint16(data) + return &opt, nil +} diff --git a/dhcpv6/options/elapsedtime_test.go b/dhcpv6/options/elapsedtime_test.go new file mode 100644 index 0000000..402d62b --- /dev/null +++ b/dhcpv6/options/elapsedtime_test.go @@ -0,0 +1,44 @@ +package options + +import ( + "bytes" + "testing" +) + +func TestOptElapsedTime(t *testing.T) { + opt, err := ParseOptElapsedTime([]byte{0xaa, 0xbb}) + if err != nil { + t.Fatal(err) + } + if optLen := opt.Length(); optLen != 2 { + t.Fatalf("Invalid length. Expected 2, got %v", optLen) + } + if elapsedTime := opt.ElapsedTime(); elapsedTime != 0xaabb { + t.Fatalf("Invalid elapsed time. Expected 0xaabb, got %v", elapsedTime) + } +} + +func TestOptElapsedTimeToBytes(t *testing.T) { + opt := OptElapsedTime{} + expected := []byte{0, 8, 0, 2, 0, 0} + if toBytes := opt.ToBytes(); !bytes.Equal(expected, toBytes) { + t.Fatalf("Invalid ToBytes output. Expected %v, got %v", expected, toBytes) + } +} + +func TestOptElapsedTimeSetGetElapsedTime(t *testing.T) { + opt := OptElapsedTime{} + opt.SetElapsedTime(10) + if elapsedTime := opt.ElapsedTime(); elapsedTime != 10 { + t.Fatalf("Invalid elapsed time. Expected 10, got %v", elapsedTime) + } +} + +func TestOptElapsedTimeString(t *testing.T) { + opt := OptElapsedTime{} + opt.SetElapsedTime(10) + expected := "OptElapsedTime{elapsedtime=10}" + if optString := opt.String(); optString != expected { + t.Fatalf("Invalid elapsed time string. Expected %v, got %v", expected, optString) + } +} diff --git a/dhcpv6/options/iaaddress.go b/dhcpv6/options/iaaddress.go new file mode 100644 index 0000000..24f28ad --- /dev/null +++ b/dhcpv6/options/iaaddress.go @@ -0,0 +1,86 @@ +package options + +// This module defines the OptIAAddress structure. +// https://www.ietf.org/rfc/rfc3633.txt + +import ( + "encoding/binary" + "fmt" + "net" +) + +type OptIAAddress struct { + ipv6Addr [16]byte + preferredLifetime uint32 + validLifetime uint32 + options []byte +} + +func (op *OptIAAddress) Code() OptionCode { + return OPTION_IAADDR +} + +func (op *OptIAAddress) ToBytes() []byte { + buf := make([]byte, 28) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_IAADDR)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + copy(buf[4:20], op.ipv6Addr[:]) + binary.BigEndian.PutUint32(buf[20:24], op.preferredLifetime) + binary.BigEndian.PutUint32(buf[24:28], op.validLifetime) + buf = append(buf, op.options...) + return buf +} + +func (op *OptIAAddress) IPv6Addr() []byte { + return op.ipv6Addr[:] +} + +func (op *OptIAAddress) SetIPv6Addr(addr [16]byte) { + op.ipv6Addr = addr +} + +func (op *OptIAAddress) PreferredLifetime() uint32 { + return op.preferredLifetime +} + +func (op *OptIAAddress) SetPreferredLifetime(pl uint32) { + op.preferredLifetime = pl +} + +func (op *OptIAAddress) ValidLifetime() uint32 { + return op.validLifetime +} + +func (op *OptIAAddress) SetValidLifetime(vl uint32) { + op.validLifetime = vl +} +func (op *OptIAAddress) Options() []byte { + return op.options +} + +func (op *OptIAAddress) SetOptions(options []byte) { + op.options = options +} + +func (op *OptIAAddress) Length() int { + return 24 + len(op.options) +} + +func (op *OptIAAddress) String() string { + return fmt.Sprintf("OptIAAddress{ipv6addr=%v, preferredlifetime=%v, validlifetime=%v, options=%v}", + net.IP(op.ipv6Addr[:]), op.preferredLifetime, op.validLifetime, op.options) +} + +// build an OptIAAddress structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptIAAddress(data []byte) (*OptIAAddress, error) { + opt := OptIAAddress{} + if len(data) < 24 { + return nil, fmt.Errorf("Invalid IA Address data length. Expected at least 24 bytes, got %v", len(data)) + } + copy(opt.ipv6Addr[:], data[:16]) + opt.preferredLifetime = binary.BigEndian.Uint32(data[16:20]) + opt.validLifetime = binary.BigEndian.Uint32(data[20:24]) + copy(opt.options, data[24:]) + return &opt, nil +} diff --git a/dhcpv6/options/iaprefix.go b/dhcpv6/options/iaprefix.go new file mode 100644 index 0000000..d91df2a --- /dev/null +++ b/dhcpv6/options/iaprefix.go @@ -0,0 +1,98 @@ +package options + +// This module defines the OptIAPrefix structure. +// https://www.ietf.org/rfc/rfc3633.txt + +import ( + "encoding/binary" + "fmt" + "net" +) + +type OptIAPrefix struct { + preferredLifetime uint32 + validLifetime uint32 + prefixLength byte + ipv6Prefix [16]byte + options []byte +} + +func (op *OptIAPrefix) Code() OptionCode { + return OPTION_IAPREFIX +} + +func (op *OptIAPrefix) ToBytes() []byte { + buf := make([]byte, 25) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_IAPREFIX)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + binary.BigEndian.PutUint32(buf[4:8], op.preferredLifetime) + binary.BigEndian.PutUint32(buf[8:12], op.validLifetime) + buf = append(buf, op.prefixLength) + buf = append(buf, op.ipv6Prefix[:]...) + buf = append(buf, op.options...) + return buf +} + +func (op *OptIAPrefix) PreferredLifetime() uint32 { + return op.preferredLifetime +} + +func (op *OptIAPrefix) SetPreferredLifetime(pl uint32) { + op.preferredLifetime = pl +} + +func (op *OptIAPrefix) ValidLifetime() uint32 { + return op.validLifetime +} + +func (op *OptIAPrefix) SetValidLifetime(vl uint32) { + op.validLifetime = vl +} + +func (op *OptIAPrefix) PrefixLength() byte { + return op.prefixLength +} + +func (op *OptIAPrefix) SetPrefixLength(pl byte) { + op.prefixLength = pl +} + +func (op *OptIAPrefix) IPv6Prefix() []byte { + return op.ipv6Prefix[:] +} + +func (op *OptIAPrefix) SetIPv6Prefix(p [16]byte) { + op.ipv6Prefix = p +} + +func (op *OptIAPrefix) Options() []byte { + return op.options +} + +func (op *OptIAPrefix) SetOptions(options []byte) { + op.options = options +} + +func (op *OptIAPrefix) Length() int { + return 25 + len(op.options) +} + +func (op *OptIAPrefix) String() string { + return fmt.Sprintf("OptIAPrefix{preferredlifetime=%v, validlifetime=%v, prefixlength=%v, ipv6prefix=%v, options=%v}", + op.preferredLifetime, op.validLifetime, op.prefixLength, net.IP(op.ipv6Prefix[:]), op.options) +} + +// build an OptIAPrefix structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptIAPrefix(data []byte) (*OptIAPrefix, error) { + opt := OptIAPrefix{} + if len(data) < 12 { + return nil, fmt.Errorf("Invalid IA for Prefix Delegation data length. Expected at least 12 bytes, got %v", len(data)) + } + opt.preferredLifetime = binary.BigEndian.Uint32(data[:4]) + opt.validLifetime = binary.BigEndian.Uint32(data[4:8]) + opt.prefixLength = data[9] + copy(opt.ipv6Prefix[:], data[9:17]) + copy(opt.options, data[17:]) + return &opt, nil +} diff --git a/dhcpv6/options/nontemporaryaddress.go b/dhcpv6/options/nontemporaryaddress.go new file mode 100644 index 0000000..703b8cc --- /dev/null +++ b/dhcpv6/options/nontemporaryaddress.go @@ -0,0 +1,86 @@ +package options + +// This module defines the OptIANA structure. +// https://www.ietf.org/rfc/rfc3633.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptIANA struct { + iaId [4]byte + t1 uint32 + t2 uint32 + options []byte +} + +func (op *OptIANA) Code() OptionCode { + return OPTION_IA_NA +} + +func (op *OptIANA) ToBytes() []byte { + buf := make([]byte, 16) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_IA_NA)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + copy(buf[4:8], op.iaId[:]) + binary.BigEndian.PutUint32(buf[8:12], op.t1) + binary.BigEndian.PutUint32(buf[12:16], op.t2) + buf = append(buf, op.options...) + return buf +} + +func (op *OptIANA) IAID() []byte { + return op.iaId[:] +} + +func (op *OptIANA) SetIAID(iaId [4]byte) { + op.iaId = iaId +} + +func (op *OptIANA) T1() uint32 { + return op.t1 +} + +func (op *OptIANA) SetT1(t1 uint32) { + op.t1 = t1 +} + +func (op *OptIANA) T2() uint32 { + return op.t2 +} + +func (op *OptIANA) SetT2(t2 uint32) { + op.t2 = t2 +} + +func (op *OptIANA) Options() []byte { + return op.options +} + +func (op *OptIANA) SetOptions(options []byte) { + op.options = options +} + +func (op *OptIANA) Length() int { + return 12 + len(op.options) +} + +func (op *OptIANA) String() string { + return fmt.Sprintf("OptIANA{IAID=%v, t1=%v, t2=%v, options=%v}", + op.iaId, op.t1, op.t2, op.options) +} + +// build an OptIANA structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptIANA(data []byte) (*OptIANA, error) { + opt := OptIANA{} + if len(data) < 12 { + return nil, fmt.Errorf("Invalid IA for Non-temporary Addresses data length. Expected at least 12 bytes, got %v", len(data)) + } + copy(opt.iaId[:], data[:4]) + opt.t1 = binary.BigEndian.Uint32(data[4:8]) + opt.t2 = binary.BigEndian.Uint32(data[8:12]) + copy(opt.options, data[12:]) + return &opt, nil +} diff --git a/dhcpv6/options/options.go b/dhcpv6/options/options.go new file mode 100644 index 0000000..6b587dd --- /dev/null +++ b/dhcpv6/options/options.go @@ -0,0 +1,125 @@ +package options + +import ( + "encoding/binary" + "fmt" +) + +type OptionCode uint16 + +type Option interface { + Code() OptionCode + ToBytes() []byte + Length() int + String() string +} + +type OptionGeneric struct { + OptionCode OptionCode + OptionData []byte +} + +func (og *OptionGeneric) Code() OptionCode { + return og.OptionCode +} + +func (og *OptionGeneric) ToBytes() []byte { + var ret []byte + codeBytes := make([]byte, 2) + binary.BigEndian.PutUint16(codeBytes, uint16(og.OptionCode)) + ret = append(ret, codeBytes...) + lengthBytes := make([]byte, 2) + binary.BigEndian.PutUint16(lengthBytes, uint16(len(og.OptionData))) + ret = append(ret, lengthBytes...) + ret = append(ret, og.OptionData...) + return ret +} + +func (og *OptionGeneric) String() string { + code, ok := OptionCodeToString[og.OptionCode] + if !ok { + code = "UnknownOption" + } + return fmt.Sprintf("%v -> %v", code, og.OptionData) +} + +func (og *OptionGeneric) Length() int { + return len(og.OptionData) +} + +func ParseOption(dataStart []byte) (Option, error) { + // Parse a sequence of bytes as a single DHCPv6 option. + // Returns the option structure, or an error if any. + if len(dataStart) < 4 { + return nil, fmt.Errorf("Invalid DHCPv6 option: less than 4 bytes") + } + code := OptionCode(binary.BigEndian.Uint16(dataStart[:2])) + length := int(binary.BigEndian.Uint16(dataStart[2:4])) + if len(dataStart) < length+4 { + return nil, fmt.Errorf("Invalid option length. Declared %v, actual %v", + length, len(dataStart)-4, + ) + } + var ( + err error + opt Option + ) + optData := dataStart[4 : 4+length] + switch code { + case OPTION_CLIENTID: + opt, err = ParseOptClientId(optData) + case OPTION_SERVERID: + opt, err = ParseOptServerId(optData) + case OPTION_ELAPSED_TIME: + opt, err = ParseOptElapsedTime(optData) + case OPTION_ORO: + opt, err = ParseOptRequestedOption(optData) + case DNS_RECURSIVE_NAME_SERVER: + opt, err = ParseOptDNSRecursiveNameServer(optData) + case DOMAIN_SEARCH_LIST: + opt, err = ParseOptDomainSearchList(optData) + case OPTION_IA_NA: + opt, err = ParseOptIANA(optData) + case OPTION_IA_PD: + opt, err = ParseOptIAForPrefixDelegation(optData) + case OPTION_IAADDR: + opt, err = ParseOptIAAddress(optData) + case OPTION_IAPREFIX: + opt, err = ParseOptIAPrefix(optData) + case OPTION_STATUS_CODE: + opt, err = ParseOptStatusCode(optData) + default: + opt = &OptionGeneric{OptionCode: code, OptionData: optData} + } + if err != nil { + return nil, err + } + return opt, nil +} + +func FromBytes(data []byte) ([]Option, error) { + // Parse a sequence of bytes until the end and build a list of options from + // it. Returns an error if any invalid option or length is found. + if len(data) < 4 { + // cannot be shorter than option code (2 bytes) + length (2 bytes) + return nil, fmt.Errorf("Invalid options: shorter than 4 bytes") + } + options := make([]Option, 0, 10) + idx := 0 + for { + if idx == len(data) { + break + } + if idx > len(data) { + // this should never happen + return nil, fmt.Errorf("Error: reading past the end of options") + } + opt, err := ParseOption(data[idx:]) + if err != nil { + return nil, err + } + options = append(options, opt) + idx += opt.Length() + 4 // 4 bytes for type + length + } + return options, nil +} diff --git a/dhcpv6/options/prefixdelegation.go b/dhcpv6/options/prefixdelegation.go new file mode 100644 index 0000000..1c02433 --- /dev/null +++ b/dhcpv6/options/prefixdelegation.go @@ -0,0 +1,86 @@ +package options + +// This module defines the OptIAForPrefixDelegation structure. +// https://www.ietf.org/rfc/rfc3633.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptIAForPrefixDelegation struct { + iaId [4]byte + t1 uint32 + t2 uint32 + options []byte +} + +func (op *OptIAForPrefixDelegation) Code() OptionCode { + return OPTION_IA_PD +} + +func (op *OptIAForPrefixDelegation) ToBytes() []byte { + buf := make([]byte, 16) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_IA_PD)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + copy(buf[4:8], op.iaId[:]) + binary.BigEndian.PutUint32(buf[8:12], op.t1) + binary.BigEndian.PutUint32(buf[12:16], op.t2) + buf = append(buf, op.options...) + return buf +} + +func (op *OptIAForPrefixDelegation) IAID() []byte { + return op.iaId[:] +} + +func (op *OptIAForPrefixDelegation) SetIAID(iaId [4]byte) { + op.iaId = iaId +} + +func (op *OptIAForPrefixDelegation) T1() uint32 { + return op.t1 +} + +func (op *OptIAForPrefixDelegation) SetT1(t1 uint32) { + op.t1 = t1 +} + +func (op *OptIAForPrefixDelegation) T2() uint32 { + return op.t2 +} + +func (op *OptIAForPrefixDelegation) SetT2(t2 uint32) { + op.t2 = t2 +} + +func (op *OptIAForPrefixDelegation) Options() []byte { + return op.options +} + +func (op *OptIAForPrefixDelegation) SetOptions(options []byte) { + op.options = options +} + +func (op *OptIAForPrefixDelegation) Length() int { + return 12 + len(op.options) +} + +func (op *OptIAForPrefixDelegation) String() string { + return fmt.Sprintf("OptIAForPrefixDelegation{IAID=%v, t1=%v, t2=%v, options=%v}", + op.iaId, op.t1, op.t2, op.options) +} + +// build an OptIAForPrefixDelegation structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptIAForPrefixDelegation(data []byte) (*OptIAForPrefixDelegation, error) { + opt := OptIAForPrefixDelegation{} + if len(data) < 12 { + return nil, fmt.Errorf("Invalid IA for Prefix Delegation data length. Expected at least 12 bytes, got %v", len(data)) + } + copy(opt.iaId[:], data[:4]) + opt.t1 = binary.BigEndian.Uint32(data[4:8]) + opt.t2 = binary.BigEndian.Uint32(data[8:12]) + opt.options = append(opt.options, data[12:]...) + return &opt, nil +} diff --git a/dhcpv6/options/requestedoption.go b/dhcpv6/options/requestedoption.go new file mode 100644 index 0000000..88e9ff3 --- /dev/null +++ b/dhcpv6/options/requestedoption.go @@ -0,0 +1,72 @@ +package options + +// This module defines the OptRequestedOption structure. +// https://www.ietf.org/rfc/rfc3315.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptRequestedOption struct { + requestedOptions []OptionCode +} + +func (op *OptRequestedOption) Code() OptionCode { + return OPTION_ORO +} + +func (op *OptRequestedOption) ToBytes() []byte { + buf := make([]byte, 4) + roBytes := make([]byte, 2) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_ORO)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + for _, ro := range op.requestedOptions { + binary.BigEndian.PutUint16(roBytes, uint16(ro)) + buf = append(buf, roBytes...) + } + return buf +} + +func (op *OptRequestedOption) RequestedOptions() []OptionCode { + return op.requestedOptions +} + +func (op *OptRequestedOption) SetRequestedOptions(opts []OptionCode) { + op.requestedOptions = opts +} + +func (op *OptRequestedOption) Length() int { + return len(op.requestedOptions) * 2 +} + +func (op *OptRequestedOption) String() string { + roString := "[" + for idx, code := range op.requestedOptions { + if name, ok := OptionCodeToString[OptionCode(code)]; ok { + roString += name + } else { + roString += "Unknown" + } + if idx < len(op.requestedOptions)-1 { + roString += ", " + } + } + roString += "]" + return fmt.Sprintf("OptRequestedOption{options=%v}", roString) +} + +// build an OptRequestedOption structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptRequestedOption(data []byte) (*OptRequestedOption, error) { + if len(data)%2 != 0 { + return nil, fmt.Errorf("Invalid OptRequestedOption data: length is not a multiple of 2") + } + opt := OptRequestedOption{} + var rOpts []OptionCode + for i := 0; i < len(data); i += 2 { + rOpts = append(rOpts, OptionCode(binary.BigEndian.Uint16(data[i:i+2]))) + } + opt.requestedOptions = rOpts + return &opt, nil +} diff --git a/dhcpv6/options/rfc1035label.go b/dhcpv6/options/rfc1035label.go new file mode 100644 index 0000000..0d2cff3 --- /dev/null +++ b/dhcpv6/options/rfc1035label.go @@ -0,0 +1,54 @@ +package options + +import ( + "fmt" + "strings" +) + +func LabelsFromBytes(buf []byte) ([]string, error) { + var ( + pos = 0 + domains = make([]string, 0) + label = "" + ) + for { + if pos >= len(buf) { + return domains, nil + } + length := int(buf[pos]) + pos++ + if length == 0 { + domains = append(domains, label) + label = "" + } + if len(buf)-pos < length { + return nil, fmt.Errorf("DomainNamesFromBytes: invalid short label length") + } + if label != "" { + label += "." + } + label += string(buf[pos : pos+length]) + pos += length + } + return domains, nil +} + +func LabelToBytes(label string) []byte { + var encodedLabel []byte + if len(label) == 0 { + return []byte{0} + } + for _, part := range strings.Split(label, ".") { + encodedLabel = append(encodedLabel, byte(len(part))) + encodedLabel = append(encodedLabel, []byte(part)...) + } + return append(encodedLabel, 0) +} + +func LabelsToBytes(labels []string) []byte { + var encodedLabels []byte + for _, label := range labels { + encodedLabels = append(encodedLabels, LabelToBytes(label)...) + } + return encodedLabels +} diff --git a/dhcpv6/options/rfc1035label_test.go b/dhcpv6/options/rfc1035label_test.go new file mode 100644 index 0000000..4e1debb --- /dev/null +++ b/dhcpv6/options/rfc1035label_test.go @@ -0,0 +1,66 @@ +package options + +import ( + "bytes" + "testing" +) + +func TestLabelsFromBytes(t *testing.T) { + labels, err := LabelsFromBytes([]byte{ + 0x9, 's', 'l', 'a', 'c', 'k', 'w', 'a', 'r', 'e', + 0x2, 'i', 't', + 0x0, + }) + if err != nil { + t.Fatal(err) + } + if len(labels) != 1 { + t.Fatalf("Invalid labels length. Expected: 1, got: %v", len(labels)) + } + if labels[0] != "slackware.it" { + t.Fatalf("Invalid label. Expected: %v, got: %v'", "slackware.it", labels[0]) + } +} + +func TestLabelsFromBytesZeroLength(t *testing.T) { + labels, err := LabelsFromBytes([]byte{}) + if err != nil { + t.Fatal(err) + } + if len(labels) != 0 { + t.Fatalf("Invalid labels length. Expected: 0, got: %v", len(labels)) + } +} + +func TestLabelsFromBytesInvalidLength(t *testing.T) { + labels, err := LabelsFromBytes([]byte{0x3, 0xaa, 0xbb}) // short length + if err == nil { + t.Fatal("Expected error, got nil") + } + if len(labels) != 0 { + t.Fatalf("Invalid labels length. Expected: 0, got: %v", len(labels)) + } + if labels != nil { + t.Fatalf("Invalid label. Expected nil, got %v", labels) + } +} + +func TestLabelToBytes(t *testing.T) { + encodedLabel := LabelToBytes("slackware.it") + expected := []byte{ + 0x9, 's', 'l', 'a', 'c', 'k', 'w', 'a', 'r', 'e', + 0x2, 'i', 't', + 0x0, + } + if !bytes.Equal(encodedLabel, expected) { + t.Fatalf("Invalid label. Expected: %v, got: %v", expected, encodedLabel) + } +} + +func TestLabelToBytesZeroLength(t *testing.T) { + encodedLabel := LabelToBytes("") + expected := []byte{0} + if !bytes.Equal(encodedLabel, expected) { + t.Fatalf("Invalid label. Expected: %v, got: %v", expected, encodedLabel) + } +} diff --git a/dhcpv6/options/serverid.go b/dhcpv6/options/serverid.go new file mode 100644 index 0000000..a66f4b4 --- /dev/null +++ b/dhcpv6/options/serverid.go @@ -0,0 +1,57 @@ +package options + +// This module defines the OptServerId and DUID structures. +// https://www.ietf.org/rfc/rfc3315.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptServerId struct { + sid Duid +} + +func (op *OptServerId) Code() OptionCode { + return OPTION_SERVERID +} + +func (op *OptServerId) ToBytes() []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_SERVERID)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + buf = append(buf, op.sid.ToBytes()...) + return buf +} + +func (op *OptServerId) ServerID() Duid { + return op.sid +} + +func (op *OptServerId) SetServerID(sid Duid) { + op.sid = sid +} + +func (op *OptServerId) Length() int { + return op.sid.Length() +} + +func (op *OptServerId) String() string { + return fmt.Sprintf("OptServerId{sid=%v}", op.sid.String()) +} + +// build an OptServerId structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptServerId(data []byte) (*OptServerId, error) { + if len(data) < 2 { + // at least the DUID type is necessary to continue + return nil, fmt.Errorf("Invalid OptServerId data: shorter than 2 bytes") + } + opt := OptServerId{} + sid, err := DuidFromBytes(data) + if err != nil { + return nil, err + } + opt.sid = *sid + return &opt, nil +} diff --git a/dhcpv6/options/statuscode.go b/dhcpv6/options/statuscode.go new file mode 100644 index 0000000..60f6f71 --- /dev/null +++ b/dhcpv6/options/statuscode.go @@ -0,0 +1,63 @@ +package options + +// This module defines the OptStatusCode structure. +// https://www.ietf.org/rfc/rfc3315.txt + +import ( + "encoding/binary" + "fmt" +) + +type OptStatusCode struct { + statusCode uint16 + statusMessage []byte +} + +func (op *OptStatusCode) Code() OptionCode { + return OPTION_STATUS_CODE +} + +func (op *OptStatusCode) ToBytes() []byte { + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], uint16(OPTION_STATUS_CODE)) + binary.BigEndian.PutUint16(buf[2:4], uint16(op.Length())) + binary.BigEndian.PutUint16(buf[4:6], op.statusCode) + buf = append(buf, op.statusMessage...) + return buf +} + +func (op *OptStatusCode) StatusCode() uint16 { + return op.statusCode +} + +func (op *OptStatusCode) SetStatusCode(code uint16) { + op.statusCode = code +} + +func (op *OptStatusCode) StatusMessage() uint16 { + return op.statusCode +} + +func (op *OptStatusCode) SetStatusMessage(message []byte) { + op.statusMessage = message +} + +func (op *OptStatusCode) Length() int { + return 2 + len(op.statusMessage) +} + +func (op *OptStatusCode) String() string { + return fmt.Sprintf("OptStatusCode{code=%v, message=%v}", op.statusCode, string(op.statusMessage)) +} + +// build an OptStatusCode structure from a sequence of bytes. +// The input data does not include option code and length bytes. +func ParseOptStatusCode(data []byte) (*OptStatusCode, error) { + if len(data) < 2 { + return nil, fmt.Errorf("Invalid OptStatusCode data: length is shorter than 2") + } + opt := OptStatusCode{} + opt.statusCode = binary.BigEndian.Uint16(data[0:2]) + opt.statusMessage = append(opt.statusMessage, data[2:]...) + return &opt, nil +} diff --git a/dhcpv6/options/types.go b/dhcpv6/options/types.go new file mode 100644 index 0000000..48f1003 --- /dev/null +++ b/dhcpv6/options/types.go @@ -0,0 +1,152 @@ +package options + +const ( + _ OptionCode = iota // skip 0 + OPTION_CLIENTID + OPTION_SERVERID + OPTION_IA_NA + OPTION_IA_TA + OPTION_IAADDR + OPTION_ORO + OPTION_PREFERENCE + OPTION_ELAPSED_TIME + OPTION_RELAY_MSG + _ // skip 10 + OPTION_AUTH + OPTION_UNICAST + OPTION_STATUS_CODE + OPTION_RAPID_COMMIT + OPTION_USER_CLASS + OPTION_VENDOR_CLASS + OPTION_VENDOR_OPTS + OPTION_INTERFACE_ID + OPTION_RECONF_MSG + OPTION_RECONF_ACCEPT + SIP_SERVERS_DOMAIN_NAME_LIST + SIP_SERVERS_IPV6_ADDRESS_LIST + DNS_RECURSIVE_NAME_SERVER + DOMAIN_SEARCH_LIST + OPTION_IA_PD + OPTION_IAPREFIX + OPTION_NIS_SERVERS + OPTION_NISP_SERVERS + OPTION_NIS_DOMAIN_NAME + OPTION_NISP_DOMAIN_NAME + SNTP_SERVER_LIST + INFORMATION_REFRESH_TIME + BCMCS_CONTROLLER_DOMAIN_NAME_LIST + BCMCS_CONTROLLER_IPV6_ADDRESS_LIST + _ // skip 35 + OPTION_GEOCONF_CIVIC + OPTION_REMOTE_ID + RELAY_AGENT_SUBSCRIBER_ID + FQDN + PANA_AUTHENTICATION_AGENT + OPTION_NEW_POSIX_TIMEZONE + OPTION_NEW_TZDB_TIMEZONE + ECHO_REQUEST + OPTION_LQ_QUERY + OPTION_CLIENT_DATA + OPTION_CLT_TIME + OPTION_LQ_RELAY_DATA + OPTION_LQ_CLIENT_LINK + MIPV6_HOME_NETWORK_ID_FQDN + MIPV6_VISITED_HOME_NETWORK_INFORMATION + LOST_SERVER + CAPWAP_ACCESS_CONTROLLER_ADDRESSES + RELAY_ID + OPTION_IPV6_ADDRESS_MOS + OPTION_IPV6_FQDN_MOS + OPTION_NTP_SERVER + OPTION_V6_ACCESS_DOMAIN + OPTION_SIP_UA_CS_LIST + OPT_BOOTFILE_URL + OPT_BOOTFILE_PARAM + OPTION_CLIENT_ARCH_TYPE + OPTION_NII + OPTION_GEOLOCATION + OPTION_AFTR_NAME + OPTION_ERP_LOCAL_DOMAIN_NAME + OPTION_RSOO + OPTION_PD_EXCLUDE + VIRTUAL_SUBNET_SELECTION + MIPV6_IDENTIFIED_HOME_NETWORK_INFORMATION + MIPV6_UNRESTRICTED_HOME_NETWORK_INFORMATION + MIPV6_HOME_NETWORK_PREFIX + MIPV6_HOME_AGENT_ADDRESS + MIPV6_HOME_AGENT_FQDN +) + +var OptionCodeToString = map[OptionCode]string{ + OPTION_CLIENTID: "OPTION_CLIENTID", + OPTION_SERVERID: "OPTION_SERVERID", + OPTION_IA_NA: "OPTION_IA_NA", + OPTION_IA_TA: "OPTION_IA_TA", + OPTION_IAADDR: "OPTION_IAADDR", + OPTION_ORO: "OPTION_ORO", + OPTION_PREFERENCE: "OPTION_PREFERENCE", + OPTION_ELAPSED_TIME: "OPTION_ELAPSED_TIME", + OPTION_RELAY_MSG: "OPTION_RELAY_MSG", + OPTION_AUTH: "OPTION_AUTH", + OPTION_UNICAST: "OPTION_UNICAST", + OPTION_STATUS_CODE: "OPTION_STATUS_CODE", + OPTION_RAPID_COMMIT: "OPTION_RAPID_COMMIT", + OPTION_USER_CLASS: "OPTION_USER_CLASS", + OPTION_VENDOR_CLASS: "OPTION_VENDOR_CLASS", + OPTION_VENDOR_OPTS: "OPTION_VENDOR_OPTS", + OPTION_INTERFACE_ID: "OPTION_INTERFACE_ID", + OPTION_RECONF_MSG: "OPTION_RECONF_MSG", + OPTION_RECONF_ACCEPT: "OPTION_RECONF_ACCEPT", + SIP_SERVERS_DOMAIN_NAME_LIST: "SIP Servers Domain Name List", + SIP_SERVERS_IPV6_ADDRESS_LIST: "SIP Servers IPv6 Address List", + DNS_RECURSIVE_NAME_SERVER: "DNS Recursive Name Server", + DOMAIN_SEARCH_LIST: "Domain Search List", + OPTION_IA_PD: "OPTION_IA_PD", + OPTION_IAPREFIX: "OPTION_IAPREFIX", + OPTION_NIS_SERVERS: "OPTION_NIS_SERVERS", + OPTION_NISP_SERVERS: "OPTION_NISP_SERVERS", + OPTION_NIS_DOMAIN_NAME: "OPTION_NIS_DOMAIN_NAME", + OPTION_NISP_DOMAIN_NAME: "OPTION_NISP_DOMAIN_NAME", + SNTP_SERVER_LIST: "SNTP Server List", + INFORMATION_REFRESH_TIME: "Information Refresh Time", + BCMCS_CONTROLLER_DOMAIN_NAME_LIST: "BCMCS Controller Domain Name List", + BCMCS_CONTROLLER_IPV6_ADDRESS_LIST: "BCMCS Controller IPv6 Address List", + OPTION_GEOCONF_CIVIC: "OPTION_GEOCONF", + OPTION_REMOTE_ID: "OPTION_REMOTE_ID", + RELAY_AGENT_SUBSCRIBER_ID: "Relay-Agent Subscriber ID", + FQDN: "FQDN", + PANA_AUTHENTICATION_AGENT: "PANA Authentication Agent", + OPTION_NEW_POSIX_TIMEZONE: "OPTION_NEW_POSIX_TIME_ZONE", + OPTION_NEW_TZDB_TIMEZONE: "OPTION_NEW_TZDB_TIMEZONE", + ECHO_REQUEST: "Echo Request", + OPTION_LQ_QUERY: "OPTION_LQ_QUERY", + OPTION_CLIENT_DATA: "OPTION_CLIENT_DATA", + OPTION_CLT_TIME: "OPTION_CLT_TIME", + OPTION_LQ_RELAY_DATA: "OPTION_LQ_RELAY_DATA", + OPTION_LQ_CLIENT_LINK: "OPTION_LQ_CLIENT_LINK", + MIPV6_HOME_NETWORK_ID_FQDN: "MIPv6 Home Network ID FQDN", + MIPV6_VISITED_HOME_NETWORK_INFORMATION: "MIPv6 Visited Home Network Information", + LOST_SERVER: "LoST Server", + CAPWAP_ACCESS_CONTROLLER_ADDRESSES: "CAPWAP Access Controller Addresses", + RELAY_ID: "RELAY_ID", + OPTION_IPV6_ADDRESS_MOS: "OPTION-IPv6_Address-MoS", + OPTION_IPV6_FQDN_MOS: "OPTION-IPv6-FQDN-MoS", + OPTION_NTP_SERVER: "OPTION_NTP_SERVER", + OPTION_V6_ACCESS_DOMAIN: "OPTION_V6_ACCESS_DOMAIN", + OPTION_SIP_UA_CS_LIST: "OPTION_SIP_UA_CS_LIST", + OPT_BOOTFILE_URL: "OPT_BOOTFILE_URL", + OPT_BOOTFILE_PARAM: "OPT_BOOTFILE_PARAM", + OPTION_CLIENT_ARCH_TYPE: "OPTION_CLIENT_ARCH_TYPE", + OPTION_NII: "OPTION_NII", + OPTION_GEOLOCATION: "OPTION_GEOLOCATION", + OPTION_AFTR_NAME: "OPTION_AFTR_NAME", + OPTION_ERP_LOCAL_DOMAIN_NAME: "OPTION_ERP_LOCAL_DOMAIN_NAME", + OPTION_RSOO: "OPTION_RSOO", + OPTION_PD_EXCLUDE: "OPTION_PD_EXCLUDE", + VIRTUAL_SUBNET_SELECTION: "Virtual Subnet Selection", + MIPV6_IDENTIFIED_HOME_NETWORK_INFORMATION: "MIPv6 Identified Home Network Information", + MIPV6_UNRESTRICTED_HOME_NETWORK_INFORMATION: "MIPv6 Unrestricted Home Network Information", + MIPV6_HOME_NETWORK_PREFIX: "MIPv6 Home Network Prefix", + MIPV6_HOME_AGENT_ADDRESS: "MIPv6 Home Agent Address", + MIPV6_HOME_AGENT_FQDN: "MIPv6 Home Agent FQDN", +} |