summaryrefslogtreecommitdiffhomepage
path: root/dhcpv6
diff options
context:
space:
mode:
authorAndrea Barberio <insomniac@slackware.it>2017-11-29 21:25:48 +0000
committerAndrea Barberio <insomniac@slackware.it>2017-12-05 23:32:27 +0000
commit1403bbe04ce275148b601c32e9551b2281110347 (patch)
tree6eacd224c2ea6b6e3865cb774970726809363d46 /dhcpv6
Initial commit
Diffstat (limited to 'dhcpv6')
-rw-r--r--dhcpv6/client.go122
-rw-r--r--dhcpv6/defaults.go6
-rw-r--r--dhcpv6/dhcpv6.go215
-rw-r--r--dhcpv6/dhcpv6_test.go145
-rw-r--r--dhcpv6/iputils.go30
-rw-r--r--dhcpv6/options/clientid.go57
-rw-r--r--dhcpv6/options/dnsrecursivenameserver.go59
-rw-r--r--dhcpv6/options/domainsearchlist.go57
-rw-r--r--dhcpv6/options/duid.go100
-rw-r--r--dhcpv6/options/elapsedtime.go52
-rw-r--r--dhcpv6/options/elapsedtime_test.go44
-rw-r--r--dhcpv6/options/iaaddress.go86
-rw-r--r--dhcpv6/options/iaprefix.go98
-rw-r--r--dhcpv6/options/nontemporaryaddress.go86
-rw-r--r--dhcpv6/options/options.go125
-rw-r--r--dhcpv6/options/prefixdelegation.go86
-rw-r--r--dhcpv6/options/requestedoption.go72
-rw-r--r--dhcpv6/options/rfc1035label.go54
-rw-r--r--dhcpv6/options/rfc1035label_test.go66
-rw-r--r--dhcpv6/options/serverid.go57
-rw-r--r--dhcpv6/options/statuscode.go63
-rw-r--r--dhcpv6/options/types.go152
-rw-r--r--dhcpv6/server.go56
-rw-r--r--dhcpv6/types.go46
24 files changed, 1934 insertions, 0 deletions
diff --git a/dhcpv6/client.go b/dhcpv6/client.go
new file mode 100644
index 0000000..787439d
--- /dev/null
+++ b/dhcpv6/client.go
@@ -0,0 +1,122 @@
+package dhcpv6
+
+import (
+ "fmt"
+ "net"
+ "time"
+)
+
+const (
+ DefaultWriteTimeout = 3 * time.Second // time to wait for write calls
+ DefaultReadTimeout = 3 * time.Second // time to wait for read calls
+ DefaultInterfaceUpTimeout = 3 * time.Second // time to wait before a network interface goes up
+ maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
+)
+
+var AllDHCPRelayAgentsAndServers = net.ParseIP("ff02::1:2")
+var AllDHCPServers = net.ParseIP("ff05::1:3")
+
+type Client struct {
+ Dialer *net.Dialer
+ ReadTimeout *time.Duration
+ WriteTimeout *time.Duration
+ LocalAddr net.Addr
+ RemoteAddr net.Addr
+}
+
+// Make a stateful DHCPv6 request
+func (c *Client) Exchange(ifname string, d *DHCPv6) ([]DHCPv6, error) {
+ conversation := make([]DHCPv6, 1)
+ var err error
+
+ // Solicit
+ if d == nil {
+ d, err = NewSolicitForInterface(ifname)
+ if err != nil {
+ return conversation, err
+ }
+ }
+ conversation[0] = *d
+ advertise, err := c.ExchangeSolicitAdvertise(ifname, d)
+ if err != nil {
+ return conversation, err
+ }
+ conversation = append(conversation, *advertise)
+
+ // TODO request/reply
+ return conversation, nil
+}
+
+func (c *Client) ExchangeSolicitAdvertise(ifname string, d *DHCPv6) (*DHCPv6, error) {
+ // if no LocalAddr is specified, get the interface's link-local address
+ var laddr net.UDPAddr
+ if c.LocalAddr == nil {
+ llAddr, err := GetLinkLocalAddr(ifname)
+ if err != nil {
+ return nil, err
+ }
+ laddr = net.UDPAddr{IP: *llAddr, Port: DefaultClientPort, Zone: ifname}
+ } else {
+ if addr, ok := c.LocalAddr.(*net.UDPAddr); ok {
+ laddr = *addr
+ } else {
+ return nil, fmt.Errorf("Invalid local address: not a net.UDPAddr: %v", c.LocalAddr)
+ }
+ }
+
+ // if no RemoteAddr is specified, use AllDHCPRelayAgentsAndServers
+ var raddr net.UDPAddr
+ if c.RemoteAddr == nil {
+ raddr = net.UDPAddr{IP: AllDHCPRelayAgentsAndServers, Port: DefaultServerPort}
+ } else {
+ if addr, ok := c.RemoteAddr.(*net.UDPAddr); ok {
+ raddr = *addr
+ } else {
+ return nil, fmt.Errorf("Invalid remote address: not a net.UDPAddr: %v", c.RemoteAddr)
+ }
+ }
+
+ // prepare the socket to listen on for replies
+ conn, err := net.ListenUDP("udp6", &laddr)
+ if err != nil {
+ return nil, err
+ }
+ defer conn.Close()
+
+ // set WriteTimeout to DefaultWriteTimeout if no other timeout is specified
+ var wtimeout time.Duration
+ if c.WriteTimeout == nil {
+ wtimeout = DefaultWriteTimeout
+ } else {
+ wtimeout = *c.WriteTimeout
+ }
+ conn.SetWriteDeadline(time.Now().Add(wtimeout))
+
+ // send the SOLICIT packet out
+ _, err = conn.WriteTo(d.ToBytes(), &raddr)
+ if err != nil {
+ return nil, err
+ }
+
+ // set ReadTimeout to DefaultReadTimeout if no other timeout is specified
+ var rtimeout time.Duration
+ if c.ReadTimeout == nil {
+ rtimeout = DefaultReadTimeout
+ } else {
+ rtimeout = *c.ReadTimeout
+ }
+ conn.SetReadDeadline(time.Now().Add(rtimeout))
+
+ // wait for an ADVERTISE response
+ buf := make([]byte, maxUDPReceivedPacketSize)
+ oobdata := []byte{} // ignoring oob data
+ n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
+ if err != nil {
+ return nil, err
+ }
+ adv, err := FromBytes(buf[:n])
+ if err != nil {
+ return nil, err
+ }
+ return adv, nil
+}
diff --git a/dhcpv6/defaults.go b/dhcpv6/defaults.go
new file mode 100644
index 0000000..ae14bba
--- /dev/null
+++ b/dhcpv6/defaults.go
@@ -0,0 +1,6 @@
+package dhcpv6
+
+const (
+ DefaultClientPort = 546
+ DefaultServerPort = 547
+)
diff --git a/dhcpv6/dhcpv6.go b/dhcpv6/dhcpv6.go
new file mode 100644
index 0000000..6b664d2
--- /dev/null
+++ b/dhcpv6/dhcpv6.go
@@ -0,0 +1,215 @@
+package dhcpv6
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "fmt"
+ "github.com/insomniacslk/dhcp/dhcpv6/options"
+ "github.com/insomniacslk/dhcp/iana"
+ "log"
+ "net"
+ "time"
+)
+
+const HeaderSize = 4
+
+type DHCPv6 struct {
+ message MessageType
+ transactionID uint32 // only 24 bits are used though
+ options []options.Option
+}
+
+func BytesToTransactionID(data []byte) (*uint32, error) {
+ // return a uint32 from a sequence of bytes, representing a transaction ID.
+ // Transaction IDs are three-bytes long. If the provided data is shorter than
+ // 3 bytes, it return an error. If longer, will use the first three bytes
+ // only.
+ if len(data) < 3 {
+ return nil, fmt.Errorf("Invalid transaction ID: less than 3 bytes")
+ }
+ buf := make([]byte, 4)
+ copy(buf[1:4], data[:3])
+ tid := binary.BigEndian.Uint32(buf)
+ return &tid, nil
+}
+
+func GenerateTransactionID() (*uint32, error) {
+ var tid *uint32
+ for {
+ tidBytes := make([]byte, 4)
+ n, err := rand.Read(tidBytes)
+ if n != 4 {
+ return nil, fmt.Errorf("Invalid random sequence: shorter than 4 bytes")
+ }
+ tid, err = BytesToTransactionID(tidBytes)
+ if err != nil {
+ return nil, err
+ }
+ if tid == nil {
+ return nil, fmt.Errorf("Error: got a nil Transaction ID")
+ }
+ // retry until != 0
+ // TODO add retry limit
+ if *tid != 0 {
+ break
+ }
+ }
+ return tid, nil
+}
+
+func FromBytes(data []byte) (*DHCPv6, error) {
+ if len(data) < HeaderSize {
+ return nil, fmt.Errorf("Invalid DHCPv6 header: shorter than %v bytes", HeaderSize)
+ }
+ tid, err := BytesToTransactionID(data[1:4])
+ if err != nil {
+ return nil, err
+ }
+ d := DHCPv6{
+ message: MessageType(data[0]),
+ transactionID: *tid,
+ }
+ options, err := options.FromBytes(data[4:])
+ if err != nil {
+ return nil, err
+ }
+ d.options = options
+ return &d, nil
+}
+
+func New() (*DHCPv6, error) {
+ tid, err := GenerateTransactionID()
+ if err != nil {
+ return nil, err
+ }
+ d := DHCPv6{
+ message: SOLICIT,
+ transactionID: *tid,
+ }
+ return &d, nil
+}
+
+// Return a time integer suitable for DUID-LLT, i.e. the current time counted in
+// seconds since January 1st, 2000, midnight UTC, modulo 2^32
+func GetTime() uint32 {
+ now := time.Since(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC))
+ return uint32((now.Nanoseconds() / 1000000000) % 0xffffffff)
+}
+
+// Create a new SOLICIT message with DUID-LLT, using the given network
+// interface's hardware address and current time
+func NewSolicitForInterface(ifname string) (*DHCPv6, error) {
+ d, err := New()
+ if err != nil {
+ return nil, err
+ }
+ d.SetMessage(SOLICIT)
+ iface, err := net.InterfaceByName(ifname)
+ if err != nil {
+ return nil, err
+ }
+ cid := options.OptClientId{}
+ cid.SetClientID(options.Duid{
+ Type: options.DUID_LLT,
+ HwType: iana.HwTypeEthernet,
+ Time: GetTime(),
+ LinkLayerAddr: iface.HardwareAddr,
+ })
+
+ d.AddOption(&cid)
+ oro := options.OptRequestedOption{}
+ oro.SetRequestedOptions([]options.OptionCode{
+ options.DNS_RECURSIVE_NAME_SERVER,
+ options.DOMAIN_SEARCH_LIST,
+ })
+ d.AddOption(&oro)
+ d.AddOption(&options.OptElapsedTime{})
+ // FIXME use real values for IA_NA
+ iaNa := options.OptIANA{}
+ iaNa.SetIAID([4]byte{0x27, 0xfe, 0x8f, 0x95})
+ iaNa.SetT1(0xe10)
+ iaNa.SetT2(0x1518)
+ d.AddOption(&iaNa)
+ return d, nil
+}
+
+func (d *DHCPv6) Message() MessageType {
+ return d.message
+}
+
+func (d *DHCPv6) SetMessage(message MessageType) {
+ if MessageToString[message] == "" {
+ log.Printf("Warning: unknown DHCPv6 message: %v", message)
+ }
+ d.message = message
+}
+
+func (d *DHCPv6) MessageToString() string {
+ if m := MessageToString[d.message]; m != "" {
+ return m
+ }
+ return "Invalid"
+}
+
+func (d *DHCPv6) TransactionID() uint32 {
+ return d.transactionID
+}
+
+func (d *DHCPv6) SetTransactionID(tid uint32) {
+ ttid := tid & 0x00ffffff
+ if ttid != tid {
+ log.Printf("Warning: truncating transaction ID that is longer than 24 bits: %v", tid)
+ }
+ d.transactionID = ttid
+}
+
+func (d *DHCPv6) Options() []options.Option {
+ return d.options
+}
+
+func (d *DHCPv6) SetOptions(options []options.Option) {
+ d.options = options
+}
+
+func (d *DHCPv6) AddOption(option options.Option) {
+ d.options = append(d.options, option)
+}
+
+func (d *DHCPv6) String() string {
+ return fmt.Sprintf("DHCPv6(message=%v transactionID=0x%06x, %d options)",
+ d.MessageToString(), d.TransactionID(), len(d.options),
+ )
+}
+
+func (d *DHCPv6) Summary() string {
+ ret := fmt.Sprintf(
+ "DHCPv6\n"+
+ " message=%v\n"+
+ " transactionid=0x%06x\n",
+ d.MessageToString(),
+ d.TransactionID(),
+ )
+ ret += " options=["
+ if len(d.options) > 0 {
+ ret += "\n"
+ }
+ for _, opt := range d.options {
+ ret += fmt.Sprintf(" %v\n", opt.String())
+ }
+ ret += " ]\n"
+ return ret
+}
+
+// Convert a DHCPv6 structure into its binary representation, suitable for being
+// sent over the network
+func (d *DHCPv6) ToBytes() []byte {
+ var ret []byte
+ ret = append(ret, byte(d.message))
+ tidBytes := make([]byte, 4)
+ binary.BigEndian.PutUint32(tidBytes, d.transactionID)
+ ret = append(ret, tidBytes[1:4]...) // discard the first byte
+ for _, opt := range d.options {
+ ret = append(ret, opt.ToBytes()...)
+ }
+ return ret
+}
diff --git a/dhcpv6/dhcpv6_test.go b/dhcpv6/dhcpv6_test.go
new file mode 100644
index 0000000..084a8b4
--- /dev/null
+++ b/dhcpv6/dhcpv6_test.go
@@ -0,0 +1,145 @@
+package dhcpv6
+
+import (
+ "bytes"
+ "github.com/insomniacslk/dhcp/dhcpv6/options"
+ "testing"
+)
+
+func TestBytesToTransactionID(t *testing.T) {
+ // only the first three bytes should be used
+ tid, err := BytesToTransactionID([]byte{0x11, 0x22, 0x33, 0xaa})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tid == nil {
+ t.Fatal("Invalid Transaction ID. Should not be nil")
+ }
+ if *tid != 0x112233 {
+ t.Fatalf("Invalid Transaction ID. Expected 0x%x, got 0x%x", 0x112233, *tid)
+ }
+}
+
+func TestBytesToTransactionIDShortData(t *testing.T) {
+ // short sequence, less than three bytes
+ tid, err := BytesToTransactionID([]byte{0x11, 0x22})
+ if err == nil {
+ t.Fatal("Expected non-nil error, got nil instead")
+ }
+ if tid != nil {
+ t.Errorf("Expected nil Transaction ID, got %v instead", *tid)
+ }
+}
+
+func TestGenerateTransactionID(t *testing.T) {
+ tid, err := GenerateTransactionID()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if tid == nil {
+ t.Fatal("Expected non-nil Transaction ID, got nil instead")
+ }
+ if *tid > 0xffffff {
+ // TODO this should be better tested by mocking the random generator
+ t.Fatalf("Invalid Transaction ID: should be smaller than 0xffffff. Got 0x%x instead", *tid)
+ }
+}
+
+func TestNew(t *testing.T) {
+ d, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if d == nil {
+ t.Fatal("Expected non-nil DHCPv6, got nil instead")
+ }
+ if d.message != SOLICIT {
+ t.Fatalf("Invalid message type. Expected %v, got %v", SOLICIT, d.message)
+ }
+ if d.transactionID == 0 {
+ t.Fatal("Invalid Transaction ID, expected non-zero, got zero")
+ }
+ if len(d.options) != 0 {
+ t.Fatalf("Invalid options: expected none, got %v", len(d.options))
+ }
+}
+
+func TestSettersAndGetters(t *testing.T) {
+ d := DHCPv6{}
+ // Message
+ d.SetMessage(SOLICIT)
+ msg := d.Message()
+ if msg != SOLICIT {
+ t.Fatalf("Invalid Message. Expected %v, got %v", SOLICIT, msg)
+ }
+ d.SetMessage(ADVERTISE)
+ msg = d.Message()
+ if msg != ADVERTISE {
+ t.Fatalf("Invalid Message. Expected %v, got %v", ADVERTISE, msg)
+ }
+ // TransactionID
+ d.SetTransactionID(12345)
+ tid := d.TransactionID()
+ if tid != 12345 {
+ t.Fatalf("Invalid Transaction ID. Expected %v, got %v", 12345, tid)
+ }
+ // Options
+ opts := d.Options()
+ if len(opts) != 0 {
+ t.Fatalf("Invalid Options. Expected empty array, got %v", opts)
+ }
+ opt := options.OptionGeneric{OptionCode: 0, OptionData: []byte{}}
+ d.SetOptions([]options.Option{&opt})
+ opts = d.Options()
+ if len(opts) != 1 {
+ t.Fatalf("Invalid Options. Expected one-element array, got %v", len(opts))
+ }
+ if _, ok := opts[0].(*options.OptionGeneric); !ok {
+ t.Fatalf("Invalid Options. Expected one OptionGeneric, got %v", opts[0])
+ }
+}
+
+func TestAddOption(t *testing.T) {
+ d := DHCPv6{}
+ opts := d.Options()
+ if len(opts) != 0 {
+ t.Fatalf("Invalid Options. Expected empty array, got %v", opts)
+ }
+ opt := options.OptionGeneric{OptionCode: 0, OptionData: []byte{}}
+ d.AddOption(&opt)
+ opts = d.Options()
+ if len(opts) != 1 {
+ t.Fatalf("Invalid Options. Expected one-element array, got %v", len(opts))
+ }
+ if _, ok := opts[0].(*options.OptionGeneric); !ok {
+ t.Fatalf("Invalid Options. Expected one OptionGeneric, got %v", opts[0])
+ }
+}
+
+func TestToBytes(t *testing.T) {
+ d := DHCPv6{}
+ d.SetMessage(SOLICIT)
+ d.SetTransactionID(0xabcdef)
+ opt := options.OptionGeneric{OptionCode: 0, OptionData: []byte{}}
+ d.AddOption(&opt)
+ toBytes := d.ToBytes()
+ expected := []byte{01, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00}
+ if !bytes.Equal(toBytes, expected) {
+ t.Fatalf("Invalid ToBytes result. Expected %v, got %v", expected, toBytes)
+ }
+}
+
+func TestFromAndToBytes(t *testing.T) {
+ expected := []byte{01, 0xab, 0xcd, 0xef, 0x00, 0x00, 0x00, 0x00}
+ d, err := FromBytes(expected)
+ if err != nil {
+ t.Fatal(err)
+ }
+ toBytes := d.ToBytes()
+ if !bytes.Equal(toBytes, expected) {
+ t.Fatalf("Invalid ToBytes result. Expected %v, got %v", expected, toBytes)
+ }
+}
+
+// TODO test NewSolicit
+// test String and Summary
diff --git a/dhcpv6/iputils.go b/dhcpv6/iputils.go
new file mode 100644
index 0000000..c3ac3aa
--- /dev/null
+++ b/dhcpv6/iputils.go
@@ -0,0 +1,30 @@
+package dhcpv6
+
+import (
+ "fmt"
+ "net"
+)
+
+func GetLinkLocalAddr(ifname string) (*net.IP, error) {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return nil, err
+ }
+ for _, iface := range ifaces {
+ if iface.Name != ifname {
+ continue
+ }
+ ifaddrs, err := iface.Addrs()
+ if err != nil {
+ return nil, err
+ }
+ for _, ifaddr := range ifaddrs {
+ if ifaddr, ok := ifaddr.(*net.IPNet); ok {
+ if ifaddr.IP.To4() == nil && ifaddr.IP.IsLinkLocalUnicast() {
+ return &ifaddr.IP, nil
+ }
+ }
+ }
+ }
+ return nil, fmt.Errorf("No link-local address found for interface %v", ifname)
+}
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",
+}
diff --git a/dhcpv6/server.go b/dhcpv6/server.go
new file mode 100644
index 0000000..e12136a
--- /dev/null
+++ b/dhcpv6/server.go
@@ -0,0 +1,56 @@
+package dhcpv6
+
+import (
+ "fmt"
+ "log"
+ "net"
+)
+
+type ResponseWriter interface {
+ LocalAddr() net.Addr
+ RemoteAddr() net.Addr
+ WriteMsg(DHCPv6) error
+ Write([]byte) (int, error)
+ Close() error
+}
+
+type Handler interface {
+ ServeDHCP(w ResponseWriter, m *DHCPv6)
+}
+
+type Server struct {
+ PacketConn net.PacketConn
+ Handler Handler
+}
+
+func (s *Server) ActivateAndServe() error {
+ if s.PacketConn == nil {
+ return fmt.Errorf("Error: no packet connection specified")
+ }
+ var pc *net.UDPConn
+ var ok bool
+ if pc, ok = s.PacketConn.(*net.UDPConn); !ok {
+ return fmt.Errorf("Error: not an UDPConn")
+ }
+ if pc == nil {
+ return fmt.Errorf("ActivateAndServe: Invalid nil PacketConn")
+ }
+ log.Print("Handling requests")
+ for {
+ rbuf := make([]byte, 1024) // FIXME this is bad
+ n, peer, err := pc.ReadFrom(rbuf)
+ if err != nil {
+ log.Printf("Error reading from packet conn: %v", err)
+ continue
+ }
+ log.Printf("Handling request from %v", peer)
+ m, err := FromBytes(rbuf[:n])
+ if err != nil {
+ log.Printf("Error parsing DHCPv6 request: %v", err)
+ continue
+ }
+ log.Print(m.Summary())
+ // FIXME use s.Handler
+ }
+ return nil
+}
diff --git a/dhcpv6/types.go b/dhcpv6/types.go
new file mode 100644
index 0000000..370243e
--- /dev/null
+++ b/dhcpv6/types.go
@@ -0,0 +1,46 @@
+package dhcpv6
+
+// from http://www.networksorcery.com/enp/protocol/dhcpv6.htm
+
+type MessageType uint8
+
+const (
+ _ MessageType = iota // skip 0
+ SOLICIT
+ ADVERTISE
+ REQUEST
+ CONFIRM
+ RENEW
+ REBIND
+ REPLY
+ RELEASE
+ DECLINE
+ RECONFIGURE
+ INFORMATION_REQUEST
+ RELAY_FORW
+ RELAY_REPL
+ LEASEQUERY
+ LEASEQUERY_REPLY
+ LEASEQUERY_DONE
+ LEASEQUERY_DATA
+)
+
+var MessageToString = map[MessageType]string{
+ SOLICIT: "SOLICIT",
+ ADVERTISE: "ADVERTISE",
+ REQUEST: "REQUEST",
+ CONFIRM: "CONFIRM",
+ RENEW: "RENEW",
+ REBIND: "REBIND",
+ REPLY: "REPLY",
+ RELEASE: "RELEASE",
+ DECLINE: "DECLINE",
+ RECONFIGURE: "RECONFIGURE",
+ INFORMATION_REQUEST: "INFORMATION-REQUEST",
+ RELAY_FORW: "RELAY-FORW",
+ RELAY_REPL: "RELAY-REPL",
+ LEASEQUERY: "LEASEQUERY",
+ LEASEQUERY_REPLY: "LEASEQUERY-REPLY",
+ LEASEQUERY_DONE: "LEASEQUERY-DONE",
+ LEASEQUERY_DATA: "LEASEQUERY-DATA",
+}