summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4
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 /dhcpv4
Initial commit
Diffstat (limited to 'dhcpv4')
-rw-r--r--dhcpv4/client.go146
-rw-r--r--dhcpv4/defaults.go6
-rw-r--r--dhcpv4/dhcpv4.go514
-rw-r--r--dhcpv4/dhcpv4_test.go333
-rw-r--r--dhcpv4/options.go102
-rw-r--r--dhcpv4/options_test.go148
-rw-r--r--dhcpv4/types.go345
7 files changed, 1594 insertions, 0 deletions
diff --git a/dhcpv4/client.go b/dhcpv4/client.go
new file mode 100644
index 0000000..34724e4
--- /dev/null
+++ b/dhcpv4/client.go
@@ -0,0 +1,146 @@
+package dhcpv4
+
+import (
+ "encoding/binary"
+ "golang.org/x/net/ipv4"
+ "net"
+ "syscall"
+ "time"
+)
+
+const (
+ maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
+)
+
+type Client struct {
+ Network string
+ Dialer *net.Dialer
+ Timeout time.Duration
+}
+
+func makeRawBroadcastPacket(payload []byte) ([]byte, error) {
+ udp := make([]byte, 8)
+ binary.BigEndian.PutUint16(udp[:2], ClientPort)
+ binary.BigEndian.PutUint16(udp[2:4], ServerPort)
+ binary.BigEndian.PutUint16(udp[4:6], uint16(8+len(payload)))
+ binary.BigEndian.PutUint16(udp[6:8], 0) // try to offload the checksum
+
+ h := ipv4.Header{
+ Version: 4,
+ Len: 20,
+ TotalLen: 20 + len(udp) + len(payload),
+ TTL: 64,
+ Protocol: 17, // UDP
+ Dst: net.IPv4bcast,
+ Src: net.IPv4zero,
+ }
+ ret, err := h.Marshal()
+ if err != nil {
+ return nil, err
+ }
+ ret = append(ret, udp...)
+ ret = append(ret, payload...)
+ return ret, nil
+}
+
+// Run a full DORA transaction: Discovery, Offer, Request, Acknowledge, over
+// UDP. Does not retry in case of failures.
+// Returns a list of DHCPv4 structures representing the exchange. It can contain
+// up to four elements, ordered as Discovery, Offer, Request and Acknowledge.
+// In case of errors, an error is returned, and the list of DHCPv4 objects will
+// be shorted than 4, containing all the sent and received DHCPv4 messages.
+func (c *Client) Exchange(d *DHCPv4, ifname string) ([]DHCPv4, error) {
+ conversation := make([]DHCPv4, 1)
+ var err error
+
+ // Discovery
+ if d == nil {
+ d, err = NewDiscovery()
+ if err != nil {
+ return conversation, err
+ }
+ iface, err := net.InterfaceByName(ifname)
+ if err != nil {
+ return conversation, err
+ }
+ d.SetClientHwAddr(iface.HardwareAddr)
+ }
+ conversation[0] = *d
+
+ fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.BindToDevice(fd, ifname)
+ if err != nil {
+ return conversation, err
+ }
+
+ daddr := syscall.SockaddrInet4{Port: ClientPort, Addr: [4]byte{255, 255, 255, 255}}
+ packet, err := makeRawBroadcastPacket(d.ToBytes())
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.Sendto(fd, packet, 0, &daddr)
+ if err != nil {
+ return conversation, err
+ }
+
+ // Offer
+ conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: ClientPort})
+ if err != nil {
+ return conversation, err
+ }
+ defer conn.Close()
+
+ buf := make([]byte, maxUDPReceivedPacketSize)
+ oobdata := []byte{} // ignoring oob data
+ n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
+ offer, err := FromBytes(buf[:n])
+ if err != nil {
+ return conversation, err
+ }
+ // TODO match the packet content
+ // TODO check that the peer address matches the declared server IP and port
+ conversation = append(conversation, *offer)
+
+ // Request
+ request, err := RequestFromOffer(*offer)
+ if err != nil {
+ return conversation, err
+ }
+ conversation = append(conversation, *request)
+ packet, err = makeRawBroadcastPacket(request.ToBytes())
+ if err != nil {
+ return conversation, err
+ }
+ err = syscall.Sendto(fd, packet, 0, &daddr)
+ if err != nil {
+ return conversation, err
+ }
+
+ // Acknowledge
+ buf = make([]byte, maxUDPReceivedPacketSize)
+ n, _, _, _, err = conn.ReadMsgUDP(buf, oobdata)
+ acknowledge, err := FromBytes(buf[:n])
+ if err != nil {
+ return conversation, err
+ }
+ // TODO match the packet content
+ // TODO check that the peer address matches the declared server IP and port
+ conversation = append(conversation, *acknowledge)
+
+ return conversation, nil
+}
diff --git a/dhcpv4/defaults.go b/dhcpv4/defaults.go
new file mode 100644
index 0000000..4faec2c
--- /dev/null
+++ b/dhcpv4/defaults.go
@@ -0,0 +1,6 @@
+package dhcpv4
+
+const (
+ ServerPort = 67
+ ClientPort = 68
+)
diff --git a/dhcpv4/dhcpv4.go b/dhcpv4/dhcpv4.go
new file mode 100644
index 0000000..7cf1da3
--- /dev/null
+++ b/dhcpv4/dhcpv4.go
@@ -0,0 +1,514 @@
+package dhcpv4
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "github.com/insomniacslk/dhcp/iana"
+ "log"
+ "net"
+ "strings"
+)
+
+const HeaderSize = 236
+const MaxMessageSize = 576
+
+type DHCPv4 struct {
+ opcode OpcodeType
+ hwType iana.HwTypeType
+ hwAddrLen uint8
+ hopCount uint8
+ transactionID uint32
+ numSeconds uint16
+ flags uint16
+ clientIPAddr net.IP
+ yourIPAddr net.IP
+ serverIPAddr net.IP
+ gatewayIPAddr net.IP
+ clientHwAddr [16]byte
+ serverHostName [64]byte
+ bootFileName [128]byte
+ options []Option
+}
+
+// Generate a random 32-bits number suitable as TransactionID
+func GenerateTransactionID() (*uint32, error) {
+ b := make([]byte, 4)
+ n, err := rand.Read(b)
+ if n != 4 {
+ return nil, errors.New("Invalid random sequence: smaller than 32 bits")
+ }
+ if err != nil {
+ return nil, err
+ }
+ tid := binary.LittleEndian.Uint32(b)
+ return &tid, nil
+}
+
+// Create a new DHCPv4 structure and fill it up with default values. It won't be
+// a valid DHCPv4 message so you will need to adjust its fields.
+// See also NewDiscovery, NewOffer, NewRequest, NewAcknowledge, NewInform and
+// NewRelease .
+func New() (*DHCPv4, error) {
+ tid, err := GenerateTransactionID()
+ if err != nil {
+ return nil, err
+ }
+ d := DHCPv4{
+ opcode: OpcodeBootRequest,
+ hwType: iana.HwTypeEthernet,
+ hwAddrLen: 6,
+ hopCount: 0,
+ transactionID: *tid,
+ numSeconds: 0,
+ flags: 0,
+ clientIPAddr: net.IPv4zero,
+ yourIPAddr: net.IPv4zero,
+ serverIPAddr: net.IPv4zero,
+ gatewayIPAddr: net.IPv4zero,
+ }
+ copy(d.clientHwAddr[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+ copy(d.serverHostName[:], []byte{})
+ copy(d.bootFileName[:], []byte{})
+ options, err := OptionsFromBytes(MagicCookie)
+ if err != nil {
+ return nil, err
+ }
+ d.options = options
+ return &d, nil
+}
+
+// Will build a new DHCPv4 Discovery message, with a default Ethernet HW type
+// and a null hardware address. The caller needs to fill the remaining fields up
+func NewDiscovery() (*DHCPv4, error) {
+ d, err := New()
+ if err != nil {
+ return nil, err
+ }
+ d.SetOpcode(OpcodeBootRequest)
+ d.SetHwType(iana.HwTypeEthernet)
+ d.SetHwAddrLen(6)
+ d.SetBroadcast()
+ d.AddOption(Option{
+ Code: OptionDHCPMessageType,
+ Data: []byte{1},
+ })
+ d.AddOption(Option{
+ Code: OptionParameterRequestList,
+ Data: []byte{OptionSubnetMask, OptionRouter, OptionDomainName, OptionDomainNameServer},
+ })
+ // the End option has to be added explicitly
+ d.AddOption(Option{Code: OptionEnd})
+ return d, nil
+}
+
+// Build a DHCPv4 request from an offer
+func RequestFromOffer(offer DHCPv4) (*DHCPv4, error) {
+ d, err := New()
+ if err != nil {
+ return nil, err
+ }
+ d.SetOpcode(OpcodeBootRequest)
+ d.SetHwType(offer.HwType())
+ d.SetHwAddrLen(offer.HwAddrLen())
+ hwaddr := offer.ClientHwAddr()
+ d.SetClientHwAddr(hwaddr[:])
+ d.SetTransactionID(offer.TransactionID())
+ if offer.IsBroadcast() {
+ d.SetBroadcast()
+ } else {
+ d.SetUnicast()
+ }
+ // find server IP address
+ var serverIP []byte
+ for _, opt := range offer.options {
+ if opt.Code == OptionServerIdentifier {
+ serverIP = make([]byte, 4)
+ copy(serverIP, opt.Data)
+ }
+ }
+ if serverIP == nil {
+ return nil, errors.New("Missing Server IP Address in DHCP Offer")
+ }
+ d.SetServerIPAddr(serverIP)
+ d.AddOption(Option{
+ Code: OptionDHCPMessageType,
+ Data: []byte{3},
+ })
+ d.AddOption(Option{
+ Code: OptionRequestedIPAddress,
+ Data: offer.YourIPAddr(),
+ })
+ d.AddOption(Option{
+ Code: OptionServerIdentifier,
+ Data: serverIP,
+ })
+ // the End option has to be added explicitly
+ d.AddOption(Option{Code: OptionEnd})
+ return d, nil
+}
+
+func FromBytes(data []byte) (*DHCPv4, error) {
+ if len(data) < HeaderSize {
+ return nil, errors.New(fmt.Sprintf("Invalid DHCPv4 header: shorter than %v bytes", HeaderSize))
+ }
+ d := DHCPv4{
+ opcode: OpcodeType(data[0]),
+ hwType: iana.HwTypeType(data[1]),
+ hwAddrLen: data[2],
+ hopCount: data[3],
+ transactionID: binary.BigEndian.Uint32(data[4:8]),
+ numSeconds: binary.BigEndian.Uint16(data[8:10]),
+ flags: binary.BigEndian.Uint16(data[10:12]),
+ clientIPAddr: net.IP(data[12:16]),
+ yourIPAddr: net.IP(data[16:20]),
+ serverIPAddr: net.IP(data[20:24]),
+ gatewayIPAddr: net.IP(data[24:28]),
+ }
+ copy(d.clientHwAddr[:], data[28:44])
+ copy(d.serverHostName[:], data[44:108])
+ copy(d.bootFileName[:], data[108:236])
+ options, err := OptionsFromBytes(data[236:])
+ if err != nil {
+ return nil, err
+ }
+ d.options = options
+ return &d, nil
+}
+
+func (d *DHCPv4) Opcode() OpcodeType {
+ return d.opcode
+}
+
+func (d *DHCPv4) OpcodeToString() string {
+ opcode := OpcodeToString[d.opcode]
+ if opcode == "" {
+ opcode = "Invalid"
+ }
+ return opcode
+}
+
+func (d *DHCPv4) SetOpcode(opcode OpcodeType) {
+ if OpcodeToString[opcode] == "" {
+ log.Printf("Warning: unknown DHCPv4 opcode: %v", opcode)
+ }
+ d.opcode = opcode
+}
+
+func (d *DHCPv4) HwType() iana.HwTypeType {
+ return d.hwType
+}
+
+func (d *DHCPv4) HwTypeToString() string {
+ hwtype, ok := iana.HwTypeToString[d.hwType]
+ if !ok {
+ hwtype = "Invalid"
+ }
+ return hwtype
+}
+
+func (d *DHCPv4) SetHwType(hwType iana.HwTypeType) {
+ if _, ok := iana.HwTypeToString[hwType]; !ok {
+ log.Printf("Warning: Invalid DHCPv4 hwtype: %v", hwType)
+ }
+ d.hwType = hwType
+}
+
+func (d *DHCPv4) HwAddrLen() uint8 {
+ return d.hwAddrLen
+}
+
+func (d *DHCPv4) SetHwAddrLen(hwAddrLen uint8) {
+ if hwAddrLen > 16 {
+ log.Printf("Warning: invalid HwAddrLen: %v > 16, using 16 instead", hwAddrLen)
+ hwAddrLen = 16
+ }
+ d.hwAddrLen = hwAddrLen
+}
+
+func (d *DHCPv4) HopCount() uint8 {
+ return d.hopCount
+}
+
+func (d *DHCPv4) SetHopCount(hopCount uint8) {
+ d.hopCount = hopCount
+}
+
+func (d *DHCPv4) TransactionID() uint32 {
+ return d.transactionID
+}
+
+func (d *DHCPv4) SetTransactionID(transactionID uint32) {
+ d.transactionID = transactionID
+}
+
+func (d *DHCPv4) NumSeconds() uint16 {
+ return d.numSeconds
+}
+
+func (d *DHCPv4) SetNumSeconds(numSeconds uint16) {
+ d.numSeconds = numSeconds
+}
+
+func (d *DHCPv4) Flags() uint16 {
+ return d.flags
+}
+
+func (d *DHCPv4) SetFlags(flags uint16) {
+ d.flags = flags
+}
+
+func (d *DHCPv4) FlagsToString() string {
+ flags := ""
+ if d.IsBroadcast() {
+ flags += "Broadcast"
+ } else {
+ flags += "Unicast"
+ }
+ if d.flags&0xfe != 0 {
+ flags += " (reserved bits not zeroed)"
+ }
+ return flags
+}
+
+func (d *DHCPv4) IsBroadcast() bool {
+ return d.flags&0x8000 == 0x8000
+}
+
+func (d *DHCPv4) SetBroadcast() {
+ d.flags |= 0x8000
+}
+
+func (d *DHCPv4) IsUnicast() bool {
+ return d.flags&0x8000 == 0
+}
+
+func (d *DHCPv4) SetUnicast() {
+ d.flags &= ^uint16(0x8000)
+}
+
+func (d *DHCPv4) ClientIPAddr() net.IP {
+ return d.clientIPAddr
+}
+
+func (d *DHCPv4) SetClientIPAddr(clientIPAddr net.IP) {
+ d.clientIPAddr = clientIPAddr
+}
+
+func (d *DHCPv4) YourIPAddr() net.IP {
+ return d.yourIPAddr
+}
+
+func (d *DHCPv4) SetYourIPAddr(yourIPAddr net.IP) {
+ d.yourIPAddr = yourIPAddr
+}
+
+func (d *DHCPv4) ServerIPAddr() net.IP {
+ return d.serverIPAddr
+}
+
+func (d *DHCPv4) SetServerIPAddr(serverIPAddr net.IP) {
+ d.serverIPAddr = serverIPAddr
+}
+
+func (d *DHCPv4) GatewayIPAddr() net.IP {
+ return d.gatewayIPAddr
+}
+
+func (d *DHCPv4) SetGatewayIPAddr(gatewayIPAddr net.IP) {
+ d.gatewayIPAddr = gatewayIPAddr
+}
+
+func (d *DHCPv4) ClientHwAddr() [16]byte {
+ return d.clientHwAddr
+}
+
+func (d *DHCPv4) ClientHwAddrToString() string {
+ var ret string
+ for _, b := range d.clientHwAddr[:d.hwAddrLen] {
+ ret += fmt.Sprintf("%02x:", b)
+ }
+ return ret[:len(ret)-1] // remove trailing `:`
+}
+
+func (d *DHCPv4) SetClientHwAddr(clientHwAddr []byte) {
+ if len(clientHwAddr) > 16 {
+ log.Printf("Warning: too long HW Address (%d bytes), truncating to 16 bytes", len(clientHwAddr))
+ clientHwAddr = clientHwAddr[:16]
+ }
+ copy(d.clientHwAddr[:len(clientHwAddr)], clientHwAddr)
+ // pad the remaining bytes, if any
+ for i := len(clientHwAddr); i < 16; i++ {
+ d.clientHwAddr[i] = 0
+ }
+}
+
+func (d *DHCPv4) ServerHostName() [64]byte {
+ return d.serverHostName
+}
+
+func (d *DHCPv4) ServerHostNameToString() string {
+ return strings.TrimRight(string(d.serverHostName[:]), "\x00")
+}
+
+func (d *DHCPv4) SetServerHostName(serverHostName []byte) {
+ if len(serverHostName) > 64 {
+ serverHostName = serverHostName[:64]
+ } else if len(serverHostName) < 64 {
+ for i := len(serverHostName) - 1; i < 64; i++ {
+ serverHostName = append(serverHostName, 0)
+ }
+ }
+ // need an array, not a slice, so let's copy it
+ var newServerHostName [64]byte
+ copy(newServerHostName[:], serverHostName)
+ d.serverHostName = newServerHostName
+}
+
+func (d *DHCPv4) BootFileName() [128]byte {
+ return d.bootFileName
+}
+
+func (d *DHCPv4) BootFileNameToString() string {
+ return strings.TrimRight(string(d.bootFileName[:]), "\x00")
+}
+
+func (d *DHCPv4) SetBootFileName(bootFileName []byte) {
+ if len(bootFileName) > 128 {
+ bootFileName = bootFileName[:128]
+ } else if len(bootFileName) < 128 {
+ for i := len(bootFileName) - 1; i < 128; i++ {
+ bootFileName = append(bootFileName, 0)
+ }
+ }
+ // need an array, not a slice, so let's copy it
+ var newBootFileName [128]byte
+ copy(newBootFileName[:], bootFileName)
+ d.bootFileName = newBootFileName
+}
+
+func (d *DHCPv4) Options() []Option {
+ return d.options
+}
+
+func (d *DHCPv4) StrippedOptions() []Option {
+ // differently from Options() this function strips away anything coming
+ // after the End option (normally just Pad options).
+ strippedOptions := []Option{}
+ for _, opt := range d.options {
+ strippedOptions = append(strippedOptions, opt)
+ if opt.Code == OptionEnd {
+ break
+ }
+ }
+ return strippedOptions
+}
+
+func (d *DHCPv4) SetOptions(options []Option) {
+ d.options = options
+}
+
+func (d *DHCPv4) AddOption(option Option) {
+ d.options = append(d.options, option)
+}
+
+func (d *DHCPv4) String() string {
+ return fmt.Sprintf("DHCPv4(opcode=%v hwtype=%v hwaddr=%v)",
+ d.OpcodeToString(), d.HwTypeToString(), d.ClientHwAddr())
+}
+
+func (d *DHCPv4) Summary() string {
+ ret := fmt.Sprintf(
+ "DHCPv4\n"+
+ " opcode=%v\n"+
+ " hwtype=%v\n"+
+ " hwaddrlen=%v\n"+
+ " hopcount=%v\n"+
+ " transactionid=0x%08x\n"+
+ " numseconds=%v\n"+
+ " flags=%v (0x%02x)\n"+
+ " clientipaddr=%v\n"+
+ " youripaddr=%v\n"+
+ " serveripaddr=%v\n"+
+ " gatewayipaddr=%v\n"+
+ " clienthwaddr=%v\n"+
+ " serverhostname=%v\n"+
+ " bootfilename=%v\n",
+ d.OpcodeToString(),
+ d.HwTypeToString(),
+ d.HwAddrLen(),
+ d.HopCount(),
+ d.TransactionID(),
+ d.NumSeconds(),
+ d.FlagsToString(),
+ d.Flags(),
+ d.ClientIPAddr(),
+ d.YourIPAddr(),
+ d.ServerIPAddr(),
+ d.GatewayIPAddr(),
+ d.ClientHwAddrToString(),
+ d.ServerHostNameToString(),
+ d.BootFileNameToString(),
+ )
+ ret += " options=\n"
+ for _, opt := range d.options {
+ ret += fmt.Sprintf(" %v\n", opt.String())
+ if opt.Code == OptionEnd {
+ break
+ }
+ }
+ return ret
+}
+
+func (d *DHCPv4) ValidateOptions() {
+ // TODO find duplicate options
+ foundOptionEnd := false
+ for _, opt := range d.options {
+ if foundOptionEnd {
+ if opt.Code == OptionEnd {
+ log.Print("Warning: found duplicate End option")
+ }
+ if opt.Code != OptionEnd && opt.Code != OptionPad {
+ name := OptionCodeToString[opt.Code]
+ log.Printf("Warning: found option %v (%v) after End option", opt.Code, name)
+ }
+ }
+ if opt.Code == OptionEnd {
+ foundOptionEnd = true
+ }
+ }
+ if !foundOptionEnd {
+ log.Print("Warning: no End option found")
+ }
+}
+
+// Convert a DHCPv4 structure into its binary representation, suitable for being
+// sent over the network
+func (d *DHCPv4) ToBytes() []byte {
+ // This won't check if the End option is present, you've been warned
+ var ret []byte
+ u32 := make([]byte, 4)
+ u16 := make([]byte, 2)
+
+ ret = append(ret, byte(d.opcode))
+ ret = append(ret, byte(d.hwType))
+ ret = append(ret, byte(d.hwAddrLen))
+ ret = append(ret, byte(d.hopCount))
+ binary.BigEndian.PutUint32(u32, d.transactionID)
+ ret = append(ret, u32...)
+ binary.BigEndian.PutUint16(u16, d.numSeconds)
+ ret = append(ret, u16...)
+ binary.BigEndian.PutUint16(u16, d.flags)
+ ret = append(ret, u16...)
+ ret = append(ret, d.clientIPAddr[:4]...)
+ ret = append(ret, d.yourIPAddr[:4]...)
+ ret = append(ret, d.serverIPAddr[:4]...)
+ ret = append(ret, d.gatewayIPAddr[:4]...)
+ ret = append(ret, d.clientHwAddr[:16]...)
+ ret = append(ret, d.serverHostName[:64]...)
+ ret = append(ret, d.bootFileName[:128]...)
+ d.ValidateOptions() // print warnings about broken options, if any
+ ret = append(ret, OptionsToBytes(d.options)...)
+ return ret
+}
diff --git a/dhcpv4/dhcpv4_test.go b/dhcpv4/dhcpv4_test.go
new file mode 100644
index 0000000..5a12207
--- /dev/null
+++ b/dhcpv4/dhcpv4_test.go
@@ -0,0 +1,333 @@
+package dhcpv4
+
+import (
+ "bytes"
+ "github.com/insomniacslk/dhcp/iana"
+ "net"
+ "testing"
+)
+
+// NOTE: if one of the following Assert* fails where expected and got values are
+// the same, you probably have to cast one of them to match the other one's
+// type, e.g. comparing int and byte, even the same value, will fail.
+func AssertEqual(t *testing.T, a, b interface{}, what string) {
+ if a != b {
+ t.Fatalf("Invalid %s. %v != %v", what, a, b)
+ }
+}
+
+func AssertEqualBytes(t *testing.T, a, b []byte, what string) {
+ if !bytes.Equal(a, b) {
+ t.Fatalf("Invalid %s. %v != %v", what, a, b)
+ }
+}
+
+func AssertEqualIPAddr(t *testing.T, a, b net.IP, what string) {
+ if !net.IP.Equal(a, b) {
+ t.Fatalf("Invalid %s. %v != %v", what, a, b)
+ }
+}
+
+func TestFromBytes(t *testing.T) {
+ data := []byte{
+ 1, // dhcp request
+ 1, // ethernet hw type
+ 6, // hw addr length
+ 3, // hop count
+ 0xaa, 0xbb, 0xcc, 0xdd, // transaction ID, big endian (network)
+ 0, 3, // number of seconds
+ 0, 1, // broadcast
+ 0, 0, 0, 0, // client IP address
+ 0, 0, 0, 0, // your IP address
+ 0, 0, 0, 0, // server IP address
+ 0, 0, 0, 0, // gateway IP address
+ 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // client MAC address + padding
+ }
+ // server host name
+ expectedHostname := []byte{}
+ for i := 0; i < 64; i++ {
+ expectedHostname = append(expectedHostname, 0)
+ }
+ data = append(data, expectedHostname...)
+ // boot file name
+ expectedBootfilename := []byte{}
+ for i := 0; i < 128; i++ {
+ expectedBootfilename = append(expectedBootfilename, 0)
+ }
+ data = append(data, expectedBootfilename...)
+ // magic cookie, then no options
+ data = append(data, []byte{99, 130, 83, 99}...)
+
+ d, err := FromBytes(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+ AssertEqual(t, d.Opcode(), OpcodeBootRequest, "opcode")
+ AssertEqual(t, d.HwType(), iana.HwTypeEthernet, "hardware type")
+ AssertEqual(t, d.HwAddrLen(), byte(6), "hardware address length")
+ AssertEqual(t, d.HopCount(), byte(3), "hop count")
+ AssertEqual(t, d.TransactionID(), uint32(0xaabbccdd), "transaction ID")
+ AssertEqual(t, d.NumSeconds(), uint16(3), "number of seconds")
+ AssertEqual(t, d.Flags(), uint16(1), "flags")
+ AssertEqualIPAddr(t, d.ClientIPAddr(), net.IPv4zero, "client IP address")
+ AssertEqualIPAddr(t, d.YourIPAddr(), net.IPv4zero, "your IP address")
+ AssertEqualIPAddr(t, d.ServerIPAddr(), net.IPv4zero, "server IP address")
+ AssertEqualIPAddr(t, d.GatewayIPAddr(), net.IPv4zero, "gateway IP address")
+ hwaddr := d.ClientHwAddr()
+ AssertEqualBytes(t, hwaddr[:], []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "flags")
+ hostname := d.ServerHostName()
+ AssertEqualBytes(t, hostname[:], expectedHostname, "server host name")
+ bootfilename := d.BootFileName()
+ AssertEqualBytes(t, bootfilename[:], expectedBootfilename, "boot file name")
+ // no need to check Magic Cookie as it is already validated in FromBytes
+ // above
+}
+
+func TestFromBytesZeroLength(t *testing.T) {
+ data := []byte{}
+ _, err := FromBytes(data)
+ if err == nil {
+ t.Fatal("Expected error, got nil")
+ }
+}
+
+func TestFromBytesShortLength(t *testing.T) {
+ data := []byte{1, 1, 6, 0}
+ _, err := FromBytes(data)
+ if err == nil {
+ t.Fatal("Expected error, got nil")
+ }
+}
+
+func TestFromBytesInvalidOptions(t *testing.T) {
+ data := []byte{
+ 1, // dhcp request
+ 1, // ethernet hw type
+ 6, // hw addr length
+ 0, // hop count
+ 0xaa, 0xbb, 0xcc, 0xdd, // transaction ID
+ 3, 0, // number of seconds
+ 1, 0, // broadcast
+ 0, 0, 0, 0, // client IP address
+ 0, 0, 0, 0, // your IP address
+ 0, 0, 0, 0, // server IP address
+ 0, 0, 0, 0, // gateway IP address
+ 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // client MAC address + padding
+ }
+ // server host name
+ for i := 0; i < 64; i++ {
+ data = append(data, 0)
+ }
+ // boot file name
+ for i := 0; i < 128; i++ {
+ data = append(data, 0)
+ }
+ // invalid magic cookie, forcing option parsing to fail
+ data = append(data, []byte{99, 130, 83, 98}...)
+ _, err := FromBytes(data)
+ if err == nil {
+ t.Fatal("Expected error, got nil")
+ }
+}
+
+func TestSettersAndGetters(t *testing.T) {
+ data := []byte{
+ 1, // dhcp request
+ 1, // ethernet hw type
+ 6, // hw addr length
+ 3, // hop count
+ 0xaa, 0xbb, 0xcc, 0xdd, // transaction ID, big endian (network)
+ 0, 3, // number of seconds
+ 0, 1, // broadcast
+ 1, 2, 3, 4, // client IP address
+ 5, 6, 7, 8, // your IP address
+ 9, 10, 11, 12, // server IP address
+ 13, 14, 15, 16, // gateway IP address
+ 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // client MAC address + padding
+ }
+ // server host name
+ expectedHostname := []byte{}
+ for i := 0; i < 64; i++ {
+ expectedHostname = append(expectedHostname, 0)
+ }
+ data = append(data, expectedHostname...)
+ // boot file name
+ expectedBootfilename := []byte{}
+ for i := 0; i < 128; i++ {
+ expectedBootfilename = append(expectedBootfilename, 0)
+ }
+ data = append(data, expectedBootfilename...)
+ // magic cookie, then no options
+ data = append(data, []byte{99, 130, 83, 99}...)
+ d, err := FromBytes(data)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // getter/setter for Opcode
+ AssertEqual(t, d.Opcode(), OpcodeBootRequest, "opcode")
+ d.SetOpcode(OpcodeBootReply)
+ AssertEqual(t, d.Opcode(), OpcodeBootReply, "opcode")
+
+ // getter/setter for HwType
+ AssertEqual(t, d.HwType(), iana.HwTypeEthernet, "hardware type")
+ d.SetHwType(iana.HwTypeARCNET)
+ AssertEqual(t, d.HwType(), iana.HwTypeARCNET, "hardware type")
+
+ // getter/setter for HwAddrLen
+ AssertEqual(t, d.HwAddrLen(), uint8(6), "hardware address length")
+ d.SetHwAddrLen(12)
+ AssertEqual(t, d.HwAddrLen(), uint8(12), "hardware address length")
+
+ // getter/setter for HopCount
+ AssertEqual(t, d.HopCount(), uint8(3), "hop count")
+ d.SetHopCount(1)
+ AssertEqual(t, d.HopCount(), uint8(1), "hop count")
+
+ // getter/setter for TransactionID
+ AssertEqual(t, d.TransactionID(), uint32(0xaabbccdd), "transaction ID")
+ d.SetTransactionID(0xeeff0011)
+ AssertEqual(t, d.TransactionID(), uint32(0xeeff0011), "transaction ID")
+
+ // getter/setter for TransactionID
+ AssertEqual(t, d.NumSeconds(), uint16(3), "number of seconds")
+ d.SetNumSeconds(15)
+ AssertEqual(t, d.NumSeconds(), uint16(15), "number of seconds")
+
+ // getter/setter for Flags
+ AssertEqual(t, d.Flags(), uint16(1), "flags")
+ d.SetFlags(0)
+ AssertEqual(t, d.Flags(), uint16(0), "flags")
+
+ // getter/setter for ClientIPAddr
+ AssertEqualIPAddr(t, d.ClientIPAddr(), net.IPv4(1, 2, 3, 4), "client IP address")
+ d.SetClientIPAddr(net.IPv4(4, 3, 2, 1))
+ AssertEqualIPAddr(t, d.ClientIPAddr(), net.IPv4(4, 3, 2, 1), "client IP address")
+
+ // getter/setter for YourIPAddr
+ AssertEqualIPAddr(t, d.YourIPAddr(), net.IPv4(5, 6, 7, 8), "your IP address")
+ d.SetYourIPAddr(net.IPv4(8, 7, 6, 5))
+ AssertEqualIPAddr(t, d.YourIPAddr(), net.IPv4(8, 7, 6, 5), "your IP address")
+
+ // getter/setter for ServerIPAddr
+ AssertEqualIPAddr(t, d.ServerIPAddr(), net.IPv4(9, 10, 11, 12), "server IP address")
+ d.SetServerIPAddr(net.IPv4(12, 11, 10, 9))
+ AssertEqualIPAddr(t, d.ServerIPAddr(), net.IPv4(12, 11, 10, 9), "server IP address")
+
+ // getter/setter for GatewayIPAddr
+ AssertEqualIPAddr(t, d.GatewayIPAddr(), net.IPv4(13, 14, 15, 16), "gateway IP address")
+ d.SetGatewayIPAddr(net.IPv4(16, 15, 14, 13))
+ AssertEqualIPAddr(t, d.GatewayIPAddr(), net.IPv4(16, 15, 14, 13), "gateway IP address")
+
+ // getter/setter for ClientHwAddr
+ hwaddr := d.ClientHwAddr()
+ AssertEqualBytes(t, hwaddr[:], []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, "client hardware address")
+ d.SetFlags(0)
+
+ // getter/setter for ServerHostName
+ serverhostname := d.ServerHostName()
+ AssertEqualBytes(t, serverhostname[:], expectedHostname, "server host name")
+ newHostname := []byte{'t', 'e', 's', 't'}
+ for i := 0; i < 60; i++ {
+ newHostname = append(newHostname, 0)
+ }
+ d.SetServerHostName(newHostname)
+ serverhostname = d.ServerHostName()
+ AssertEqualBytes(t, serverhostname[:], newHostname, "server host name")
+
+ // getter/setter for BootFileName
+ bootfilename := d.BootFileName()
+ AssertEqualBytes(t, bootfilename[:], expectedBootfilename, "boot file name")
+ newBootfilename := []byte{'t', 'e', 's', 't'}
+ for i := 0; i < 124; i++ {
+ newBootfilename = append(newBootfilename, 0)
+ }
+ d.SetBootFileName(newBootfilename)
+ bootfilename = d.BootFileName()
+ AssertEqualBytes(t, bootfilename[:], newBootfilename, "boot file name")
+}
+
+func TestToStringMethods(t *testing.T) {
+ d, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // OpcodeToString
+ d.SetOpcode(OpcodeBootRequest)
+ AssertEqual(t, d.OpcodeToString(), "BootRequest", "OpcodeToString")
+ d.SetOpcode(OpcodeBootReply)
+ AssertEqual(t, d.OpcodeToString(), "BootReply", "OpcodeToString")
+ d.SetOpcode(OpcodeType(0))
+ AssertEqual(t, d.OpcodeToString(), "Invalid", "OpcodeToString")
+
+ // HwTypeToString
+ d.SetHwType(iana.HwTypeEthernet)
+ AssertEqual(t, d.HwTypeToString(), "Ethernet", "HwTypeToString")
+ d.SetHwType(iana.HwTypeARCNET)
+ AssertEqual(t, d.HwTypeToString(), "ARCNET", "HwTypeToString")
+
+ // FlagsToString
+ d.SetUnicast()
+ AssertEqual(t, d.FlagsToString(), "Unicast", "FlagsToString")
+ d.SetBroadcast()
+ AssertEqual(t, d.FlagsToString(), "Broadcast", "FlagsToString")
+ d.SetFlags(0xffff)
+ AssertEqual(t, d.FlagsToString(), "Broadcast (reserved bits not zeroed)", "FlagsToString")
+
+ // ClientHwAddrToString
+ d.SetHwAddrLen(6)
+ d.SetClientHwAddr([]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
+ AssertEqual(t, d.ClientHwAddrToString(), "aa:bb:cc:dd:ee:ff", "ClientHwAddrToString")
+
+ // ServerHostNameToString
+ d.SetServerHostName([]byte("my.host.local"))
+ AssertEqual(t, d.ServerHostNameToString(), "my.host.local", "ServerHostNameToString")
+
+ // BootFileNameToString
+ d.SetBootFileName([]byte("/my/boot/file"))
+ AssertEqual(t, d.BootFileNameToString(), "/my/boot/file", "BootFileNameToString")
+}
+
+func TestToBytes(t *testing.T) {
+ // the following bytes match what dhcpv4.New would create. Keep them in
+ // sync!
+ expected := []byte{
+ 1, // Opcode BootRequest
+ 1, // HwType Ethernet
+ 6, // HwAddrLen
+ 0, // HopCount
+ 0x11, 0x22, 0x33, 0x44, // TransactionID
+ 0, 0, // NumSeconds
+ 0, 0, // Flags
+ 0, 0, 0, 0, // ClientIPAddr
+ 0, 0, 0, 0, // YourIPAddr
+ 0, 0, 0, 0, // ServerIPAddr
+ 0, 0, 0, 0, // GatewayIPAddr
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ClientHwAddr
+ }
+ // ServerHostName
+ for i := 0; i < 64; i++ {
+ expected = append(expected, 0)
+ }
+ // BootFileName
+ for i := 0; i < 128; i++ {
+ expected = append(expected, 0)
+ }
+ // Magic Cookie
+ expected = append(expected, MagicCookie...)
+
+ d, err := New()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // fix TransactionID to match the expected one, since it's randomly
+ // generated in New()
+ d.SetTransactionID(0x11223344)
+ got := d.ToBytes()
+ AssertEqualBytes(t, expected, got, "ToBytes")
+}
+
+// TODO
+// test broadcast/unicast flags
+// test Options setter/getter
+// test Summary() and String()
diff --git a/dhcpv4/options.go b/dhcpv4/options.go
new file mode 100644
index 0000000..59f2c83
--- /dev/null
+++ b/dhcpv4/options.go
@@ -0,0 +1,102 @@
+package dhcpv4
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+)
+
+type OptionCode byte
+
+var MagicCookie = []byte{99, 130, 83, 99}
+
+type Option struct {
+ Code OptionCode
+ Data []byte
+}
+
+func ParseOption(dataStart []byte) (*Option, error) {
+ // Parse a sequence of bytes as a single DHCPv4 option.
+ // Returns the option code, its data, and an error if any.
+ if len(dataStart) == 0 {
+ return nil, errors.New("Invalid zero-length DHCPv4 option")
+ }
+ opt := OptionCode(dataStart[0])
+ switch opt {
+ case OptionPad, OptionEnd:
+ return &Option{Code: opt, Data: []byte{}}, nil
+ default:
+ length := int(dataStart[1])
+ if len(dataStart) < length+2 {
+ return nil, errors.New(
+ fmt.Sprintf("Invalid data length. Declared %v, actual %v",
+ length, len(dataStart),
+ ))
+ }
+ data := dataStart[2 : 2+length]
+ return &Option{Code: opt, Data: data}, nil
+ }
+}
+
+func OptionsFromBytes(data []byte) ([]Option, error) {
+ // Parse a sequence of bytes until the end and build a list of options from
+ // it. The sequence must contain the Magic Cookie.
+ // Returns an error if any invalid option or length is found.
+ if len(data) < 4 {
+ return nil, errors.New("Invalid options: shorter than 4 bytes")
+ }
+ if !bytes.Equal(data[:4], MagicCookie) {
+ return nil, errors.New(fmt.Sprintf("Invalid Magic Cookie: %v", data[:4]))
+ }
+ options := make([]Option, 0, 10)
+ idx := 4
+ for {
+ if idx == len(data) {
+ break
+ }
+ if idx > len(data) {
+ // this should never happen
+ return nil, errors.New("Error: Reading past the end of options")
+ }
+ opt, err := ParseOption(data[idx:])
+ idx++
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, *opt)
+ if len(opt.Data) > 0 {
+ // options with zero length have no length byte, so here we handle the ones with
+ // nonzero length
+ idx++
+ }
+ idx += len(opt.Data)
+ }
+ return options, nil
+}
+
+func OptionsToBytes(options []Option) []byte {
+ // Convert a list of options to a wire-format representation. This will
+ // include the Magic Cookie
+ ret := MagicCookie
+ for _, opt := range options {
+ ret = append(ret, opt.ToBytes()...)
+ }
+ return ret
+}
+
+func (o *Option) String() string {
+ code, ok := OptionCodeToString[o.Code]
+ if !ok {
+ code = "Unknown"
+ }
+ return fmt.Sprintf("%v -> %v", code, o.Data)
+}
+
+func (o *Option) ToBytes() []byte {
+ // Convert a single option to its wire-format representation
+ ret := []byte{byte(o.Code)}
+ if o.Code != OptionPad && o.Code != OptionEnd {
+ ret = append(ret, byte(len(o.Data)))
+ }
+ return append(ret, o.Data...)
+}
diff --git a/dhcpv4/options_test.go b/dhcpv4/options_test.go
new file mode 100644
index 0000000..07be9bf
--- /dev/null
+++ b/dhcpv4/options_test.go
@@ -0,0 +1,148 @@
+package dhcpv4
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestParseOption(t *testing.T) {
+ option := []byte{5, 4, 192, 168, 1, 254} // DNS option
+ opt, err := ParseOption(option)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if opt.Code != OptionNameServer {
+ t.Fatalf("Invalid option code. Expected 5, got %v", opt.Code)
+ }
+ if !bytes.Equal(opt.Data, option[2:]) {
+ t.Fatalf("Invalid option data. Expected %v, got %v", option[2:], opt.Data)
+ }
+}
+
+func TestParseOptionPad(t *testing.T) {
+ option := []byte{0}
+ opt, err := ParseOption(option)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if opt.Code != OptionPad {
+ t.Fatalf("Invalid option code. Expected %v, got %v", OptionPad, opt.Code)
+ }
+ if len(opt.Data) != 0 {
+ t.Fatalf("Invalid option data. Expected empty slice, got %v", opt.Data)
+ }
+}
+
+func TestParseOptionZeroLength(t *testing.T) {
+ option := []byte{}
+ _, err := ParseOption(option)
+ if err == nil {
+ t.Fatal("Expected an error, got none")
+ }
+}
+
+func TestParseOptionShortOption(t *testing.T) {
+ option := []byte{53, 1}
+ _, err := ParseOption(option)
+ if err == nil {
+ t.Fatal(err)
+ }
+}
+
+func TestOptionsFromBytes(t *testing.T) {
+ options := []byte{
+ 99, 130, 83, 99, // Magic Cookie
+ 5, 4, 192, 168, 1, 1, // DNS
+ 255, // end
+ 0, 0, 0, //padding
+ }
+ opts, err := OptionsFromBytes(options)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // each padding byte counts as an option. Magic Cookie doesn't add up
+ if len(opts) != 5 {
+ t.Fatal("Invalid options length. Expected 5, got %v", len(opts))
+ }
+ if opts[0].Code != OptionNameServer {
+ t.Fatal("Invalid option code. Expected %v, got %v", OptionNameServer, opts[0].Code)
+ }
+ if !bytes.Equal(opts[0].Data, options[6:10]) {
+ t.Fatal("Invalid option data. Expected %v, got %v", options[6:10], opts[0].Data)
+ }
+ if opts[1].Code != OptionEnd {
+ t.Fatalf("Invalid option code. Expected %v, got %v", OptionEnd, opts[1].Code)
+ }
+ if opts[2].Code != OptionPad {
+ t.Fatalf("Invalid option code. Expected %v, got %v", OptionPad, opts[2].Code)
+ }
+}
+
+func TestOptionsFromBytesZeroLength(t *testing.T) {
+ options := []byte{}
+ _, err := OptionsFromBytes(options)
+ if err == nil {
+ t.Fatal("Expected an error, got none")
+ }
+}
+
+func TestOptionsFromBytesBadMagicCookie(t *testing.T) {
+ options := []byte{1, 2, 3, 4}
+ _, err := OptionsFromBytes(options)
+ if err == nil {
+ t.Fatal("Expected an error, got none")
+ }
+}
+
+func TestOptionsToBytes(t *testing.T) {
+ originalOptions := []byte{
+ 99, 130, 83, 99, // Magic Cookie
+ 5, 4, 192, 168, 1, 1, // DNS
+ 255, // end
+ 0, 0, 0, //padding
+ }
+ options, err := OptionsFromBytes(originalOptions)
+ if err != nil {
+ t.Fatal(err)
+ }
+ finalOptions := OptionsToBytes(options)
+ if !bytes.Equal(originalOptions, finalOptions) {
+ t.Fatalf("Invalid options. Expected %v, got %v", originalOptions, finalOptions)
+ }
+}
+
+func TestOptionsToBytesEmpty(t *testing.T) {
+ originalOptions := []byte{99, 130, 83, 99}
+ options, err := OptionsFromBytes(originalOptions)
+ if err != nil {
+ t.Fatal(err)
+ }
+ finalOptions := OptionsToBytes(options)
+ if !bytes.Equal(originalOptions, finalOptions) {
+ t.Fatalf("Invalid options. Expected %v, got %v", originalOptions, finalOptions)
+ }
+}
+
+func TestOptionsToStringPad(t *testing.T) {
+ option := []byte{0}
+ opt, err := ParseOption(option)
+ if err != nil {
+ t.Fatal(err)
+ }
+ stropt := opt.String()
+ if stropt != "Pad -> []" {
+ t.Fatalf("Invalid string representation: %v", stropt)
+ }
+}
+
+func TestOptionsToStringDHCPMessageType(t *testing.T) {
+ option := []byte{53, 1, 5}
+ opt, err := ParseOption(option)
+ if err != nil {
+ t.Fatal(err)
+ }
+ stropt := opt.String()
+ if stropt != "DHCP Message Type -> [5]" {
+ t.Fatalf("Invalid string representation: %v", stropt)
+ }
+}
diff --git a/dhcpv4/types.go b/dhcpv4/types.go
new file mode 100644
index 0000000..c0fcfe1
--- /dev/null
+++ b/dhcpv4/types.go
@@ -0,0 +1,345 @@
+package dhcpv4
+
+// values from http://www.networksorcery.com/enp/protocol/dhcp.htm and
+// http://www.networksorcery.com/enp/protocol/bootp/options.htm
+
+type OpcodeType uint8
+
+const (
+ _ OpcodeType = iota // skip 0
+ OpcodeBootRequest
+ OpcodeBootReply
+)
+
+var OpcodeToString = map[OpcodeType]string{
+ OpcodeBootRequest: "BootRequest",
+ OpcodeBootReply: "BootReply",
+}
+
+// DHCPv4 Options
+const (
+ OptionPad OptionCode = 0
+ OptionSubnetMask = 1
+ OptionTimeOffset = 2
+ OptionRouter = 3
+ OptionTimeServer = 4
+ OptionNameServer = 5
+ OptionDomainNameServer = 6
+ OptionLogServer = 7
+ OptionQuoteServer = 8
+ OptionLPRServer = 9
+ OptionImpressServer = 10
+ OptionResourceLocationServer = 11
+ OptionHostName = 12
+ OptionBootFileSize = 13
+ OptionMeritDumpFile = 14
+ OptionDomainName = 15
+ OptionSwapServer = 16
+ OptionRootPath = 17
+ OptionExtensionsPath = 18
+ OptionIPForwarding = 19
+ OptionNonLocalSourceRouting = 20
+ OptionPolicyFilter = 21
+ OptionMaximumDatagramAssemblySize = 22
+ OptionDefaultIPTTL = 23
+ OptionPathMTUAgingTimeout = 24
+ OptionPathMTUPlateauTable = 25
+ OptionInterfaceMTU = 26
+ OptionAllSubnetsAreLocal = 27
+ OptionBroadcastAddress = 28
+ OptionPerformMaskDiscovery = 29
+ OptionMaskSupplier = 30
+ OptionPerformRouterDiscovery = 31
+ OptionRouterSolicitationAddress = 32
+ OptionStaticRoutingTable = 33
+ OptionTrailerEncapsulation = 34
+ OptionArpCacheTimeout = 35
+ OptionEthernetEncapsulation = 36
+ OptionDefaulTCPTTL = 37
+ OptionTCPKeepaliveInterval = 38
+ OptionTCPKeepaliveGarbage = 39
+ OptionNetworkInformationServiceDomain = 40
+ OptionNetworkInformationServers = 41
+ OptionNTPServers = 42
+ OptionVendorSpecificInformation = 43
+ OptionNetBIOSOverTCPIPNameServer = 44
+ OptionNetBIOSOverTCPIPDatagramDistributionServer = 45
+ OptionNetBIOSOverTCPIPNodeType = 46
+ OptionNetBIOSOverTCPIPScope = 47
+ OptionXWindowSystemFontServer = 48
+ OptionXWindowSystemDisplayManger = 49
+ OptionRequestedIPAddress = 50
+ OptionIPAddressLeaseTime = 51
+ OptionOptionOverload = 52
+ OptionDHCPMessageType = 53
+ OptionServerIdentifier = 54
+ OptionParameterRequestList = 55
+ OptionMessage = 56
+ OptionMaximumDHCPMessageSize = 57
+ OptionRenewTimeValue = 58
+ OptionRebindingTimeValue = 59
+ OptionClassIdentifier = 60
+ OptionClientIdentifier = 61
+ OptionNetWareIPDomainName = 62
+ OptionNetWareIPInformation = 63
+ OptionNetworkInformationServicePlusDomain = 64
+ OptionNetworkInformationServicePlusServers = 65
+ OptionTFTPServerName = 66
+ OptionBootfileName = 67
+ OptionMobileIPHomeAgent = 68
+ OptionSimpleMailTransportProtocolServer = 69
+ OptionPostOfficeProtocolServer = 70
+ OptionNetworkNewsTransportProtocolServer = 71
+ OptionDefaultWorldWideWebServer = 72
+ OptionDefaultFingerServer = 73
+ OptionDefaultInternetRelayChatServer = 74
+ OptionStreetTalkServer = 75
+ OptionStreetTalkDirectoryAssistanceServer = 76
+ OptionUserClassInformation = 77
+ OptionSLPDirectoryAgent = 78
+ OptionSLPServiceScope = 79
+ OptionRapidCommit = 80
+ OptionFQDN = 81
+ OptionRelayAgentInformation = 82
+ OptionInternetStorageNameService = 83
+ // Option 84 returned in RFC 3679
+ OptionNDSServers = 85
+ OptionNDSTreeName = 86
+ OptionNDSContext = 87
+ OptionBCMCSControllerDomainNameList = 88
+ OptionBCMCSControllerIPv4AddressList = 89
+ OptionAuthentication = 90
+ OptionClientLastTransactionTime = 91
+ OptionAssociatedIP = 92
+ OptionClientSystemArchitectureType = 93
+ OptionClientNetworkInterfaceIdentifier = 94
+ OptionLDAP = 95
+ // Option 96 returned in RFC 3679
+ OptionClientMachineIdentifier = 97
+ OptionOpenGroupUserAuthentication = 98
+ OptionGeoConfCivic = 99
+ OptionIEEE10031TZString = 100
+ OptionReferenceToTZDatabase = 101
+ // Options 102-111 returned in RFC 3679
+ OptionNetInfoParentServerAddress = 112
+ OptionNetInfoParentServerTag = 113
+ OptionURL = 114
+ // Option 115 returned in RFC 3679
+ OptionAutoConfigure = 116
+ OptionNameServiceSearch = 117
+ OptionSubnetSelection = 118
+ OptionDNSDomainSearchList = 119
+ OptionSIPServersDHCPOption = 120
+ OptionClasslessStaticRouteOption = 121
+ OptionCCC = 122
+ OptionGeoConf = 123
+ OptionVendorIdentifyingVendorClass = 124
+ OptionVendorIdentifyingVendorSpecific = 125
+ // Options 126-127 returned in RFC 3679
+ OptionTFTPServerIPAddress = 128
+ OptionCallServerIPAddress = 129
+ OptionDiscriminationString = 130
+ OptionRemoteStatisticsServerIPAddress = 131
+ Option8021PVLANID = 132
+ Option8021QL2Priority = 133
+ OptionDiffservCodePoint = 134
+ OptionHTTPProxyForPhoneSpecificApplications = 135
+ OptionPANAAuthenticationAgent = 136
+ OptionLoSTServer = 137
+ OptionCAPWAPAccessControllerAddresses = 138
+ OptionOPTIONIPv4AddressMoS = 139
+ OptionOPTIONIPv4FQDNMoS = 140
+ OptionSIPUAConfigurationServiceDomains = 141
+ OptionOPTIONIPv4AddressANDSF = 142
+ OptionOPTIONIPv6AddressANDSF = 143
+ // Options 144-149 returned in RFC 3679
+ OptionTFTPServerAddress = 150
+ OptionStatusCode = 151
+ OptionBaseTime = 152
+ OptionStartTimeOfState = 153
+ OptionQueryStartTime = 154
+ OptionQueryEndTime = 155
+ OptionDHCPState = 156
+ OptionDataSource = 157
+ // Options 158-174 returned in RFC 3679
+ OptionEtherboot = 175
+ OptionIPTelephone = 176
+ OptionEtherbootPacketCableAndCableHome = 177
+ // Options 178-207 returned in RFC 3679
+ OptionPXELinuxMagicString = 208
+ OptionPXELinuxConfigFile = 209
+ OptionPXELinuxPathPrefix = 210
+ OptionPXELinuxRebootTime = 211
+ OptionOPTION6RD = 212
+ OptionOPTIONv4AccessDomain = 213
+ // Options 214-219 returned in RFC 3679
+ OptionSubnetAllocation = 220
+ OptionVirtualSubnetAllocation = 221
+ // Options 222-223 returned in RFC 3679
+ // Options 224-254 are reserved for private use
+ OptionEnd = 255
+)
+
+var OptionCodeToString = map[OptionCode]string{
+ OptionPad: "Pad",
+ OptionSubnetMask: "Subnet Mask",
+ OptionTimeOffset: "Time Offset",
+ OptionRouter: "Router",
+ OptionTimeServer: "Time Server",
+ OptionNameServer: "Name Server",
+ OptionDomainNameServer: "Domain Name Server",
+ OptionLogServer: "Log Server",
+ OptionQuoteServer: "Quote Server",
+ OptionLPRServer: "LPR Server",
+ OptionImpressServer: "Impress Server",
+ OptionResourceLocationServer: "Resource Location Server",
+ OptionHostName: "Host Name",
+ OptionBootFileSize: "Boot File Size",
+ OptionMeritDumpFile: "Merit Dump File",
+ OptionDomainName: "Domain Name",
+ OptionSwapServer: "Swap Server",
+ OptionRootPath: "Root Path",
+ OptionExtensionsPath: "Extensions Path",
+ OptionIPForwarding: "IP Forwarding enable/disable",
+ OptionNonLocalSourceRouting: "Non-local Source Routing enable/disable",
+ OptionPolicyFilter: "Policy Filter",
+ OptionMaximumDatagramAssemblySize: "Maximum Datagram Reassembly Size",
+ OptionDefaultIPTTL: "Default IP Time-to-live",
+ OptionPathMTUAgingTimeout: "Path MTU Aging Timeout",
+ OptionPathMTUPlateauTable: "Path MTU Plateau Table",
+ OptionInterfaceMTU: "Interface MTU",
+ OptionAllSubnetsAreLocal: "All Subnets Are Local",
+ OptionBroadcastAddress: "Broadcast Address",
+ OptionPerformMaskDiscovery: "Perform Mask Discovery",
+ OptionMaskSupplier: "Mask Supplier",
+ OptionPerformRouterDiscovery: "Perform Router Discovery",
+ OptionRouterSolicitationAddress: "Router Solicitation Address",
+ OptionStaticRoutingTable: "Static Routing Table",
+ OptionTrailerEncapsulation: "Trailer Encapsulation",
+ OptionArpCacheTimeout: "ARP Cache Timeout",
+ OptionEthernetEncapsulation: "Ethernet Encapsulation",
+ OptionDefaulTCPTTL: "Default TCP TTL",
+ OptionTCPKeepaliveInterval: "TCP Keepalive Interval",
+ OptionTCPKeepaliveGarbage: "TCP Keepalive Garbage",
+ OptionNetworkInformationServiceDomain: "Network Information Service Domain",
+ OptionNetworkInformationServers: "Network Information Servers",
+ OptionNTPServers: "NTP Servers",
+ OptionVendorSpecificInformation: "Vendor Specific Information",
+ OptionNetBIOSOverTCPIPNameServer: "NetBIOS over TCP/IP Name Server",
+ OptionNetBIOSOverTCPIPDatagramDistributionServer: "NetBIOS over TCP/IP Datagram Distribution Server",
+ OptionNetBIOSOverTCPIPNodeType: "NetBIOS over TCP/IP Node Type",
+ OptionNetBIOSOverTCPIPScope: "NetBIOS over TCP/IP Scope",
+ OptionXWindowSystemFontServer: "X Window System Font Server",
+ OptionXWindowSystemDisplayManger: "X Window System Display Manager",
+ OptionRequestedIPAddress: "Requested IP Address",
+ OptionIPAddressLeaseTime: "IP Addresses Lease Time",
+ OptionOptionOverload: "Option Overload",
+ OptionDHCPMessageType: "DHCP Message Type",
+ OptionServerIdentifier: "Server Identifier",
+ OptionParameterRequestList: "Parameter Request List",
+ OptionMessage: "Message",
+ OptionMaximumDHCPMessageSize: "Maximum DHCP Message Size",
+ OptionRenewTimeValue: "Renew Time Value",
+ OptionRebindingTimeValue: "Rebinding Time Value",
+ OptionClassIdentifier: "Class Identifier",
+ OptionClientIdentifier: "Client identifier",
+ OptionNetWareIPDomainName: "NetWare/IP Domain Name",
+ OptionNetWareIPInformation: "NetWare/IP Information",
+ OptionNetworkInformationServicePlusDomain: "Network Information Service+ Domain",
+ OptionNetworkInformationServicePlusServers: "Network Information Service+ Servers",
+ OptionTFTPServerName: "TFTP Server Name",
+ OptionBootfileName: "Bootfile Name",
+ OptionMobileIPHomeAgent: "Mobile IP Home Agent",
+ OptionSimpleMailTransportProtocolServer: "SMTP Server",
+ OptionPostOfficeProtocolServer: "POP Server",
+ OptionNetworkNewsTransportProtocolServer: "NNTP Server",
+ OptionDefaultWorldWideWebServer: "Default WWW Server",
+ OptionDefaultFingerServer: "Default Finger Server",
+ OptionDefaultInternetRelayChatServer: "Default IRC Server",
+ OptionStreetTalkServer: "StreetTalk Server",
+ OptionStreetTalkDirectoryAssistanceServer: "StreetTalk Directory Assistance Server",
+ OptionUserClassInformation: "User Class Information",
+ OptionSLPDirectoryAgent: "SLP DIrectory Agent",
+ OptionSLPServiceScope: "SLP Service Scope",
+ OptionRapidCommit: "Rapid Commit",
+ OptionFQDN: "FQDN",
+ OptionRelayAgentInformation: "Relay Agent Information",
+ OptionInternetStorageNameService: "Internet Storage Name Service",
+ // Option 84 returned in RFC 3679
+ OptionNDSServers: "NDS Servers",
+ OptionNDSTreeName: "NDS Tree Name",
+ OptionNDSContext: "NDS Context",
+ OptionBCMCSControllerDomainNameList: "BCMCS Controller Domain Name List",
+ OptionBCMCSControllerIPv4AddressList: "BCMCS Controller IPv4 Address List",
+ OptionAuthentication: "Authentication",
+ OptionClientLastTransactionTime: "Client Last Transaction Time",
+ OptionAssociatedIP: "Associated IP",
+ OptionClientSystemArchitectureType: "Client System Architecture Type",
+ OptionClientNetworkInterfaceIdentifier: "Client Network Interface Identifier",
+ OptionLDAP: "LDAP",
+ // Option 96 returned in RFC 3679
+ OptionClientMachineIdentifier: "Client Machine Identifier",
+ OptionOpenGroupUserAuthentication: "OpenGroup's User Authentication",
+ OptionGeoConfCivic: "GEOCONF_CIVIC",
+ OptionIEEE10031TZString: "IEEE 1003.1 TZ String",
+ OptionReferenceToTZDatabase: "Reference to the TZ Database",
+ // Options 102-111 returned in RFC 3679
+ OptionNetInfoParentServerAddress: "NetInfo Parent Server Address",
+ OptionNetInfoParentServerTag: "NetInfo Parent Server Tag",
+ OptionURL: "URL",
+ // Option 115 returned in RFC 3679
+ OptionAutoConfigure: "Auto-Configure",
+ OptionNameServiceSearch: "Name Service Search",
+ OptionSubnetSelection: "Subnet Selection",
+ OptionDNSDomainSearchList: "DNS Domain Search List",
+ OptionSIPServersDHCPOption: "SIP Servers DHCP Option",
+ OptionClasslessStaticRouteOption: "Classless Static Route Option",
+ OptionCCC: "CCC, CableLabs Client Configuration",
+ OptionGeoConf: "GeoConf",
+ OptionVendorIdentifyingVendorClass: "Vendor-Identifying Vendor Class",
+ OptionVendorIdentifyingVendorSpecific: "Vendor-Identifying Vendor-Specific",
+ // Options 126-127 returned in RFC 3679
+ OptionTFTPServerIPAddress: "TFTP Server IP Address",
+ OptionCallServerIPAddress: "Call Server IP Address",
+ OptionDiscriminationString: "Discrimination String",
+ OptionRemoteStatisticsServerIPAddress: "RemoteStatistics Server IP Address",
+ Option8021PVLANID: "802.1P VLAN ID",
+ Option8021QL2Priority: "802.1Q L2 Priority",
+ OptionDiffservCodePoint: "Diffserv Code Point",
+ OptionHTTPProxyForPhoneSpecificApplications: "HTTP Proxy for phone-specific applications",
+ OptionPANAAuthenticationAgent: "PANA Authentication Agent",
+ OptionLoSTServer: "LoST Server",
+ OptionCAPWAPAccessControllerAddresses: "CAPWAP Access Controller Addresses",
+ OptionOPTIONIPv4AddressMoS: "OPTION-IPv4_Address-MoS",
+ OptionOPTIONIPv4FQDNMoS: "OPTION-IPv4_FQDN-MoS",
+ OptionSIPUAConfigurationServiceDomains: "SIP UA Configuration Service Domains",
+ OptionOPTIONIPv4AddressANDSF: "OPTION-IPv4_Address-ANDSF",
+ OptionOPTIONIPv6AddressANDSF: "OPTION-IPv6_Address-ANDSF",
+ // Options 144-149 returned in RFC 3679
+ OptionTFTPServerAddress: "TFTP Server Address",
+ OptionStatusCode: "Status Code",
+ OptionBaseTime: "Base Time",
+ OptionStartTimeOfState: "Start Time of State",
+ OptionQueryStartTime: "Query Start Time",
+ OptionQueryEndTime: "Query End Time",
+ OptionDHCPState: "DHCP Staet",
+ OptionDataSource: "Data Source",
+ // Options 158-174 returned in RFC 3679
+ OptionEtherboot: "Etherboot",
+ OptionIPTelephone: "IP Telephone",
+ OptionEtherbootPacketCableAndCableHome: "Etherboot / PacketCable and CableHome",
+ // Options 178-207 returned in RFC 3679
+ OptionPXELinuxMagicString: "PXELinux Magic String",
+ OptionPXELinuxConfigFile: "PXELinux Config File",
+ OptionPXELinuxPathPrefix: "PXELinux Path Prefix",
+ OptionPXELinuxRebootTime: "PXELinux Reboot Time",
+ OptionOPTION6RD: "OPTION_6RD",
+ OptionOPTIONv4AccessDomain: "OPTION_V4_ACCESS_DOMAIN",
+ // Options 214-219 returned in RFC 3679
+ OptionSubnetAllocation: "Subnet Allocation",
+ OptionVirtualSubnetAllocation: "Virtual Subnet Selection",
+ // Options 222-223 returned in RFC 3679
+ // Options 224-254 are reserved for private use
+
+ OptionEnd: "End",
+}