| // Copyright 2016 Google, Inc. All rights reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the LICENSE file in the root of the source |
| // tree. |
| |
| package layers |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "fmt" |
| "net" |
| |
| "github.com/google/gopacket" |
| ) |
| |
| // DHCPOp rerprents a bootp operation |
| type DHCPOp byte |
| |
| // bootp operations |
| const ( |
| DHCPOpRequest DHCPOp = 1 |
| DHCPOpReply DHCPOp = 2 |
| ) |
| |
| // String returns a string version of a DHCPOp. |
| func (o DHCPOp) String() string { |
| switch o { |
| case DHCPOpRequest: |
| return "Request" |
| case DHCPOpReply: |
| return "Reply" |
| default: |
| return "Unknown" |
| } |
| } |
| |
| // DHCPMsgType represents a DHCP operation |
| type DHCPMsgType byte |
| |
| // Constants that represent DHCP operations |
| const ( |
| DHCPMsgTypeUnspecified DHCPMsgType = iota |
| DHCPMsgTypeDiscover |
| DHCPMsgTypeOffer |
| DHCPMsgTypeRequest |
| DHCPMsgTypeDecline |
| DHCPMsgTypeAck |
| DHCPMsgTypeNak |
| DHCPMsgTypeRelease |
| DHCPMsgTypeInform |
| ) |
| |
| // String returns a string version of a DHCPMsgType. |
| func (o DHCPMsgType) String() string { |
| switch o { |
| case DHCPMsgTypeUnspecified: |
| return "Unspecified" |
| case DHCPMsgTypeDiscover: |
| return "Discover" |
| case DHCPMsgTypeOffer: |
| return "Offer" |
| case DHCPMsgTypeRequest: |
| return "Request" |
| case DHCPMsgTypeDecline: |
| return "Decline" |
| case DHCPMsgTypeAck: |
| return "Ack" |
| case DHCPMsgTypeNak: |
| return "Nak" |
| case DHCPMsgTypeRelease: |
| return "Release" |
| case DHCPMsgTypeInform: |
| return "Inform" |
| default: |
| return "Unknown" |
| } |
| } |
| |
| //DHCPMagic is the RFC 2131 "magic cooke" for DHCP. |
| var DHCPMagic uint32 = 0x63825363 |
| |
| // DHCPv4 contains data for a single DHCP packet. |
| type DHCPv4 struct { |
| BaseLayer |
| Operation DHCPOp |
| HardwareType LinkType |
| HardwareLen uint8 |
| HardwareOpts uint8 |
| Xid uint32 |
| Secs uint16 |
| Flags uint16 |
| ClientIP net.IP |
| YourClientIP net.IP |
| NextServerIP net.IP |
| RelayAgentIP net.IP |
| ClientHWAddr net.HardwareAddr |
| ServerName []byte |
| File []byte |
| Options DHCPOptions |
| } |
| |
| // DHCPOptions is used to get nicely printed option lists which would normally |
| // be cut off after 5 options. |
| type DHCPOptions []DHCPOption |
| |
| // String returns a string version of the options list. |
| func (o DHCPOptions) String() string { |
| buf := &bytes.Buffer{} |
| buf.WriteByte('[') |
| for i, opt := range o { |
| buf.WriteString(opt.String()) |
| if i+1 != len(o) { |
| buf.WriteString(", ") |
| } |
| } |
| buf.WriteByte(']') |
| return buf.String() |
| } |
| |
| // LayerType returns gopacket.LayerTypeDHCPv4 |
| func (d *DHCPv4) LayerType() gopacket.LayerType { return LayerTypeDHCPv4 } |
| |
| // DecodeFromBytes decodes the given bytes into this layer. |
| func (d *DHCPv4) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error { |
| if len(data) < 240 { |
| df.SetTruncated() |
| return fmt.Errorf("DHCPv4 length %d too short", len(data)) |
| } |
| d.Options = d.Options[:0] |
| d.Operation = DHCPOp(data[0]) |
| d.HardwareType = LinkType(data[1]) |
| d.HardwareLen = data[2] |
| d.HardwareOpts = data[3] |
| d.Xid = binary.BigEndian.Uint32(data[4:8]) |
| d.Secs = binary.BigEndian.Uint16(data[8:10]) |
| d.Flags = binary.BigEndian.Uint16(data[10:12]) |
| d.ClientIP = net.IP(data[12:16]) |
| d.YourClientIP = net.IP(data[16:20]) |
| d.NextServerIP = net.IP(data[20:24]) |
| d.RelayAgentIP = net.IP(data[24:28]) |
| d.ClientHWAddr = net.HardwareAddr(data[28 : 28+d.HardwareLen]) |
| d.ServerName = data[44:108] |
| d.File = data[108:236] |
| if binary.BigEndian.Uint32(data[236:240]) != DHCPMagic { |
| return InvalidMagicCookie |
| } |
| |
| if len(data) <= 240 { |
| // DHCP Packet could have no option (??) |
| return nil |
| } |
| |
| options := data[240:] |
| |
| stop := len(options) |
| start := 0 |
| for start < stop { |
| o := DHCPOption{} |
| if err := o.decode(options[start:]); err != nil { |
| return err |
| } |
| if o.Type == DHCPOptEnd { |
| break |
| } |
| d.Options = append(d.Options, o) |
| // Check if the option is a single byte pad |
| if o.Type == DHCPOptPad { |
| start++ |
| } else { |
| start += int(o.Length) + 2 |
| } |
| } |
| |
| d.Contents = data |
| |
| return nil |
| } |
| |
| // Len returns the length of a DHCPv4 packet. |
| func (d *DHCPv4) Len() uint16 { |
| n := uint16(240) |
| for _, o := range d.Options { |
| if o.Type == DHCPOptPad { |
| n++ |
| } else { |
| n += uint16(o.Length) + 2 |
| } |
| } |
| n++ // for opt end |
| return n |
| } |
| |
| // SerializeTo writes the serialized form of this layer into the |
| // SerializationBuffer, implementing gopacket.SerializableLayer. |
| // See the docs for gopacket.SerializableLayer for more info. |
| func (d *DHCPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error { |
| plen := int(d.Len()) |
| |
| data, err := b.PrependBytes(plen) |
| if err != nil { |
| return err |
| } |
| |
| data[0] = byte(d.Operation) |
| data[1] = byte(d.HardwareType) |
| if opts.FixLengths { |
| d.HardwareLen = uint8(len(d.ClientHWAddr)) |
| } |
| data[2] = d.HardwareLen |
| data[3] = d.HardwareOpts |
| binary.BigEndian.PutUint32(data[4:8], d.Xid) |
| binary.BigEndian.PutUint16(data[8:10], d.Secs) |
| binary.BigEndian.PutUint16(data[10:12], d.Flags) |
| copy(data[12:16], d.ClientIP.To4()) |
| copy(data[16:20], d.YourClientIP.To4()) |
| copy(data[20:24], d.NextServerIP.To4()) |
| copy(data[24:28], d.RelayAgentIP.To4()) |
| copy(data[28:44], d.ClientHWAddr) |
| copy(data[44:108], d.ServerName) |
| copy(data[108:236], d.File) |
| binary.BigEndian.PutUint32(data[236:240], DHCPMagic) |
| |
| if len(d.Options) > 0 { |
| offset := 240 |
| for _, o := range d.Options { |
| if err := o.encode(data[offset:]); err != nil { |
| return err |
| } |
| // A pad option is only a single byte |
| if o.Type == DHCPOptPad { |
| offset++ |
| } else { |
| offset += 2 + len(o.Data) |
| } |
| } |
| optend := NewDHCPOption(DHCPOptEnd, nil) |
| if err := optend.encode(data[offset:]); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // CanDecode returns the set of layer types that this DecodingLayer can decode. |
| func (d *DHCPv4) CanDecode() gopacket.LayerClass { |
| return LayerTypeDHCPv4 |
| } |
| |
| // NextLayerType returns the layer type contained by this DecodingLayer. |
| func (d *DHCPv4) NextLayerType() gopacket.LayerType { |
| return gopacket.LayerTypePayload |
| } |
| |
| func decodeDHCPv4(data []byte, p gopacket.PacketBuilder) error { |
| dhcp := &DHCPv4{} |
| err := dhcp.DecodeFromBytes(data, p) |
| if err != nil { |
| return err |
| } |
| p.AddLayer(dhcp) |
| return p.NextDecoder(gopacket.LayerTypePayload) |
| } |
| |
| // DHCPOpt represents a DHCP option or parameter from RFC-2132 |
| type DHCPOpt byte |
| |
| // Constants for the DHCPOpt options. |
| const ( |
| DHCPOptPad DHCPOpt = 0 |
| DHCPOptSubnetMask DHCPOpt = 1 // 4, net.IP |
| DHCPOptTimeOffset DHCPOpt = 2 // 4, int32 (signed seconds from UTC) |
| DHCPOptRouter DHCPOpt = 3 // n*4, [n]net.IP |
| DHCPOptTimeServer DHCPOpt = 4 // n*4, [n]net.IP |
| DHCPOptNameServer DHCPOpt = 5 // n*4, [n]net.IP |
| DHCPOptDNS DHCPOpt = 6 // n*4, [n]net.IP |
| DHCPOptLogServer DHCPOpt = 7 // n*4, [n]net.IP |
| DHCPOptCookieServer DHCPOpt = 8 // n*4, [n]net.IP |
| DHCPOptLPRServer DHCPOpt = 9 // n*4, [n]net.IP |
| DHCPOptImpressServer DHCPOpt = 10 // n*4, [n]net.IP |
| DHCPOptResLocServer DHCPOpt = 11 // n*4, [n]net.IP |
| DHCPOptHostname DHCPOpt = 12 // n, string |
| DHCPOptBootfileSize DHCPOpt = 13 // 2, uint16 |
| DHCPOptMeritDumpFile DHCPOpt = 14 // >1, string |
| DHCPOptDomainName DHCPOpt = 15 // n, string |
| DHCPOptSwapServer DHCPOpt = 16 // n*4, [n]net.IP |
| DHCPOptRootPath DHCPOpt = 17 // n, string |
| DHCPOptExtensionsPath DHCPOpt = 18 // n, string |
| DHCPOptIPForwarding DHCPOpt = 19 // 1, bool |
| DHCPOptSourceRouting DHCPOpt = 20 // 1, bool |
| DHCPOptPolicyFilter DHCPOpt = 21 // 8*n, [n]{net.IP/net.IP} |
| DHCPOptDatagramMTU DHCPOpt = 22 // 2, uint16 |
| DHCPOptDefaultTTL DHCPOpt = 23 // 1, byte |
| DHCPOptPathMTUAgingTimeout DHCPOpt = 24 // 4, uint32 |
| DHCPOptPathPlateuTableOption DHCPOpt = 25 // 2*n, []uint16 |
| DHCPOptInterfaceMTU DHCPOpt = 26 // 2, uint16 |
| DHCPOptAllSubsLocal DHCPOpt = 27 // 1, bool |
| DHCPOptBroadcastAddr DHCPOpt = 28 // 4, net.IP |
| DHCPOptMaskDiscovery DHCPOpt = 29 // 1, bool |
| DHCPOptMaskSupplier DHCPOpt = 30 // 1, bool |
| DHCPOptRouterDiscovery DHCPOpt = 31 // 1, bool |
| DHCPOptSolicitAddr DHCPOpt = 32 // 4, net.IP |
| DHCPOptStaticRoute DHCPOpt = 33 // n*8, [n]{net.IP/net.IP} -- note the 2nd is router not mask |
| DHCPOptARPTrailers DHCPOpt = 34 // 1, bool |
| DHCPOptARPTimeout DHCPOpt = 35 // 4, uint32 |
| DHCPOptEthernetEncap DHCPOpt = 36 // 1, bool |
| DHCPOptTCPTTL DHCPOpt = 37 // 1, byte |
| DHCPOptTCPKeepAliveInt DHCPOpt = 38 // 4, uint32 |
| DHCPOptTCPKeepAliveGarbage DHCPOpt = 39 // 1, bool |
| DHCPOptNISDomain DHCPOpt = 40 // n, string |
| DHCPOptNISServers DHCPOpt = 41 // 4*n, [n]net.IP |
| DHCPOptNTPServers DHCPOpt = 42 // 4*n, [n]net.IP |
| DHCPOptVendorOption DHCPOpt = 43 // n, [n]byte // may be encapsulated. |
| DHCPOptNetBIOSTCPNS DHCPOpt = 44 // 4*n, [n]net.IP |
| DHCPOptNetBIOSTCPDDS DHCPOpt = 45 // 4*n, [n]net.IP |
| DHCPOptNETBIOSTCPNodeType DHCPOpt = 46 // 1, magic byte |
| DHCPOptNetBIOSTCPScope DHCPOpt = 47 // n, string |
| DHCPOptXFontServer DHCPOpt = 48 // n, string |
| DHCPOptXDisplayManager DHCPOpt = 49 // n, string |
| DHCPOptRequestIP DHCPOpt = 50 // 4, net.IP |
| DHCPOptLeaseTime DHCPOpt = 51 // 4, uint32 |
| DHCPOptExtOptions DHCPOpt = 52 // 1, 1/2/3 |
| DHCPOptMessageType DHCPOpt = 53 // 1, 1-7 |
| DHCPOptServerID DHCPOpt = 54 // 4, net.IP |
| DHCPOptParamsRequest DHCPOpt = 55 // n, []byte |
| DHCPOptMessage DHCPOpt = 56 // n, 3 |
| DHCPOptMaxMessageSize DHCPOpt = 57 // 2, uint16 |
| DHCPOptT1 DHCPOpt = 58 // 4, uint32 |
| DHCPOptT2 DHCPOpt = 59 // 4, uint32 |
| DHCPOptClassID DHCPOpt = 60 // n, []byte |
| DHCPOptClientID DHCPOpt = 61 // n >= 2, []byte |
| DHCPOptDomainSearch DHCPOpt = 119 // n, string |
| DHCPOptSIPServers DHCPOpt = 120 // n, url |
| DHCPOptClasslessStaticRoute DHCPOpt = 121 // |
| DHCPOptMUDURLV4 DHCPOpt = 161 // n, string |
| DHCPOptEnd DHCPOpt = 255 |
| ) |
| |
| // String returns a string version of a DHCPOpt. |
| func (o DHCPOpt) String() string { |
| switch o { |
| case DHCPOptPad: |
| return "(padding)" |
| case DHCPOptSubnetMask: |
| return "SubnetMask" |
| case DHCPOptTimeOffset: |
| return "TimeOffset" |
| case DHCPOptRouter: |
| return "Router" |
| case DHCPOptTimeServer: |
| return "rfc868" // old time server protocol stringified to dissuade confusion w. NTP |
| case DHCPOptNameServer: |
| return "ien116" // obscure nameserver protocol stringified to dissuade confusion w. DNS |
| case DHCPOptDNS: |
| return "DNS" |
| case DHCPOptLogServer: |
| return "mitLCS" // MIT LCS server protocol yada yada w. Syslog |
| case DHCPOptCookieServer: |
| return "CookieServer" |
| case DHCPOptLPRServer: |
| return "LPRServer" |
| case DHCPOptImpressServer: |
| return "ImpressServer" |
| case DHCPOptResLocServer: |
| return "ResourceLocationServer" |
| case DHCPOptHostname: |
| return "Hostname" |
| case DHCPOptBootfileSize: |
| return "BootfileSize" |
| case DHCPOptMeritDumpFile: |
| return "MeritDumpFile" |
| case DHCPOptDomainName: |
| return "DomainName" |
| case DHCPOptSwapServer: |
| return "SwapServer" |
| case DHCPOptRootPath: |
| return "RootPath" |
| case DHCPOptExtensionsPath: |
| return "ExtensionsPath" |
| case DHCPOptIPForwarding: |
| return "IPForwarding" |
| case DHCPOptSourceRouting: |
| return "SourceRouting" |
| case DHCPOptPolicyFilter: |
| return "PolicyFilter" |
| case DHCPOptDatagramMTU: |
| return "DatagramMTU" |
| case DHCPOptDefaultTTL: |
| return "DefaultTTL" |
| case DHCPOptPathMTUAgingTimeout: |
| return "PathMTUAgingTimeout" |
| case DHCPOptPathPlateuTableOption: |
| return "PathPlateuTableOption" |
| case DHCPOptInterfaceMTU: |
| return "InterfaceMTU" |
| case DHCPOptAllSubsLocal: |
| return "AllSubsLocal" |
| case DHCPOptBroadcastAddr: |
| return "BroadcastAddress" |
| case DHCPOptMaskDiscovery: |
| return "MaskDiscovery" |
| case DHCPOptMaskSupplier: |
| return "MaskSupplier" |
| case DHCPOptRouterDiscovery: |
| return "RouterDiscovery" |
| case DHCPOptSolicitAddr: |
| return "SolicitAddr" |
| case DHCPOptStaticRoute: |
| return "StaticRoute" |
| case DHCPOptARPTrailers: |
| return "ARPTrailers" |
| case DHCPOptARPTimeout: |
| return "ARPTimeout" |
| case DHCPOptEthernetEncap: |
| return "EthernetEncap" |
| case DHCPOptTCPTTL: |
| return "TCPTTL" |
| case DHCPOptTCPKeepAliveInt: |
| return "TCPKeepAliveInt" |
| case DHCPOptTCPKeepAliveGarbage: |
| return "TCPKeepAliveGarbage" |
| case DHCPOptNISDomain: |
| return "NISDomain" |
| case DHCPOptNISServers: |
| return "NISServers" |
| case DHCPOptNTPServers: |
| return "NTPServers" |
| case DHCPOptVendorOption: |
| return "VendorOption" |
| case DHCPOptNetBIOSTCPNS: |
| return "NetBIOSOverTCPNS" |
| case DHCPOptNetBIOSTCPDDS: |
| return "NetBiosOverTCPDDS" |
| case DHCPOptNETBIOSTCPNodeType: |
| return "NetBIOSOverTCPNodeType" |
| case DHCPOptNetBIOSTCPScope: |
| return "NetBIOSOverTCPScope" |
| case DHCPOptXFontServer: |
| return "XFontServer" |
| case DHCPOptXDisplayManager: |
| return "XDisplayManager" |
| case DHCPOptEnd: |
| return "(end)" |
| case DHCPOptSIPServers: |
| return "SipServers" |
| case DHCPOptRequestIP: |
| return "RequestIP" |
| case DHCPOptLeaseTime: |
| return "LeaseTime" |
| case DHCPOptExtOptions: |
| return "ExtOpts" |
| case DHCPOptMessageType: |
| return "MessageType" |
| case DHCPOptServerID: |
| return "ServerID" |
| case DHCPOptParamsRequest: |
| return "ParamsRequest" |
| case DHCPOptMessage: |
| return "Message" |
| case DHCPOptMaxMessageSize: |
| return "MaxDHCPSize" |
| case DHCPOptT1: |
| return "Timer1" |
| case DHCPOptT2: |
| return "Timer2" |
| case DHCPOptClassID: |
| return "ClassID" |
| case DHCPOptClientID: |
| return "ClientID" |
| case DHCPOptDomainSearch: |
| return "DomainSearch" |
| case DHCPOptClasslessStaticRoute: |
| return "ClasslessStaticRoute" |
| case DHCPOptMUDURLV4: |
| return "ManufacturerUsageDescriptionURL" |
| default: |
| return "Unknown" |
| } |
| } |
| |
| // DHCPOption rerpresents a DHCP option. |
| type DHCPOption struct { |
| Type DHCPOpt |
| Length uint8 |
| Data []byte |
| } |
| |
| // String returns a string version of a DHCP Option. |
| func (o DHCPOption) String() string { |
| switch o.Type { |
| |
| case DHCPOptHostname, DHCPOptMeritDumpFile, DHCPOptDomainName, DHCPOptRootPath, |
| DHCPOptExtensionsPath, DHCPOptNISDomain, DHCPOptNetBIOSTCPScope, DHCPOptXFontServer, |
| DHCPOptXDisplayManager, DHCPOptMessage, DHCPOptDomainSearch: // string |
| return fmt.Sprintf("Option(%s:%s)", o.Type, string(o.Data)) |
| |
| case DHCPOptMessageType: |
| if len(o.Data) != 1 { |
| return fmt.Sprintf("Option(%s:INVALID)", o.Type) |
| } |
| return fmt.Sprintf("Option(%s:%s)", o.Type, DHCPMsgType(o.Data[0])) |
| |
| case DHCPOptSubnetMask, DHCPOptServerID, DHCPOptBroadcastAddr, |
| DHCPOptSolicitAddr, DHCPOptRequestIP: // net.IP |
| if len(o.Data) < 4 { |
| return fmt.Sprintf("Option(%s:INVALID)", o.Type) |
| } |
| return fmt.Sprintf("Option(%s:%s)", o.Type, net.IP(o.Data)) |
| |
| case DHCPOptT1, DHCPOptT2, DHCPOptLeaseTime, DHCPOptPathMTUAgingTimeout, |
| DHCPOptARPTimeout, DHCPOptTCPKeepAliveInt: // uint32 |
| if len(o.Data) != 4 { |
| return fmt.Sprintf("Option(%s:INVALID)", o.Type) |
| } |
| return fmt.Sprintf("Option(%s:%d)", o.Type, |
| uint32(o.Data[0])<<24|uint32(o.Data[1])<<16|uint32(o.Data[2])<<8|uint32(o.Data[3])) |
| |
| case DHCPOptParamsRequest: |
| buf := &bytes.Buffer{} |
| buf.WriteString(fmt.Sprintf("Option(%s:", o.Type)) |
| for i, v := range o.Data { |
| buf.WriteString(DHCPOpt(v).String()) |
| if i+1 != len(o.Data) { |
| buf.WriteByte(',') |
| } |
| } |
| buf.WriteString(")") |
| return buf.String() |
| |
| default: |
| return fmt.Sprintf("Option(%s:%v)", o.Type, o.Data) |
| } |
| } |
| |
| // NewDHCPOption constructs a new DHCPOption with a given type and data. |
| func NewDHCPOption(t DHCPOpt, data []byte) DHCPOption { |
| o := DHCPOption{Type: t} |
| if data != nil { |
| o.Data = data |
| o.Length = uint8(len(data)) |
| } |
| return o |
| } |
| |
| func (o *DHCPOption) encode(b []byte) error { |
| switch o.Type { |
| case DHCPOptPad, DHCPOptEnd: |
| b[0] = byte(o.Type) |
| default: |
| b[0] = byte(o.Type) |
| b[1] = o.Length |
| copy(b[2:], o.Data) |
| } |
| return nil |
| } |
| |
| func (o *DHCPOption) decode(data []byte) error { |
| if len(data) < 1 { |
| // Pad/End have a length of 1 |
| return DecOptionNotEnoughData |
| } |
| o.Type = DHCPOpt(data[0]) |
| switch o.Type { |
| case DHCPOptPad, DHCPOptEnd: |
| o.Data = nil |
| default: |
| if len(data) < 2 { |
| return DecOptionNotEnoughData |
| } |
| o.Length = data[1] |
| if int(o.Length) > len(data[2:]) { |
| return DecOptionMalformed |
| } |
| o.Data = data[2 : 2+int(o.Length)] |
| } |
| return nil |
| } |
| |
| // DHCPv4Error is used for constant errors for DHCPv4. It is needed for test asserts. |
| type DHCPv4Error string |
| |
| // DHCPv4Error implements error interface. |
| func (d DHCPv4Error) Error() string { |
| return string(d) |
| } |
| |
| const ( |
| // DecOptionNotEnoughData is returned when there is not enough data during option's decode process |
| DecOptionNotEnoughData = DHCPv4Error("Not enough data to decode") |
| // DecOptionMalformed is returned when the option is malformed |
| DecOptionMalformed = DHCPv4Error("Option is malformed") |
| // InvalidMagicCookie is returned when Magic cookie is missing into BOOTP header |
| InvalidMagicCookie = DHCPv4Error("Bad DHCP header") |
| ) |