summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/dhcpv4.go
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpv4/dhcpv4.go')
-rw-r--r--dhcpv4/dhcpv4.go514
1 files changed, 514 insertions, 0 deletions
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
+}