diff options
author | Andrea Barberio <insomniac@slackware.it> | 2017-11-29 21:25:48 +0000 |
---|---|---|
committer | Andrea Barberio <insomniac@slackware.it> | 2017-12-05 23:32:27 +0000 |
commit | 1403bbe04ce275148b601c32e9551b2281110347 (patch) | |
tree | 6eacd224c2ea6b6e3865cb774970726809363d46 /dhcpv4 |
Initial commit
Diffstat (limited to 'dhcpv4')
-rw-r--r-- | dhcpv4/client.go | 146 | ||||
-rw-r--r-- | dhcpv4/defaults.go | 6 | ||||
-rw-r--r-- | dhcpv4/dhcpv4.go | 514 | ||||
-rw-r--r-- | dhcpv4/dhcpv4_test.go | 333 | ||||
-rw-r--r-- | dhcpv4/options.go | 102 | ||||
-rw-r--r-- | dhcpv4/options_test.go | 148 | ||||
-rw-r--r-- | dhcpv4/types.go | 345 |
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", +} |