1// Copyright 2016 Google, Inc. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style license
4// that can be found in the LICENSE file in the root of the source
5// tree.
6
7package layers
8
9import (
10	"bytes"
11	"encoding/binary"
12	"fmt"
13	"net"
14
15	"github.com/google/gopacket"
16)
17
18// DHCPOp rerprents a bootp operation
19type DHCPOp byte
20
21// bootp operations
22const (
23	DHCPOpRequest DHCPOp = 1
24	DHCPOpReply   DHCPOp = 2
25)
26
27// String returns a string version of a DHCPOp.
28func (o DHCPOp) String() string {
29	switch o {
30	case DHCPOpRequest:
31		return "Request"
32	case DHCPOpReply:
33		return "Reply"
34	default:
35		return "Unknown"
36	}
37}
38
39// DHCPMsgType represents a DHCP operation
40type DHCPMsgType byte
41
42// Constants that represent DHCP operations
43const (
44	DHCPMsgTypeUnspecified DHCPMsgType = iota
45	DHCPMsgTypeDiscover
46	DHCPMsgTypeOffer
47	DHCPMsgTypeRequest
48	DHCPMsgTypeDecline
49	DHCPMsgTypeAck
50	DHCPMsgTypeNak
51	DHCPMsgTypeRelease
52	DHCPMsgTypeInform
53)
54
55// String returns a string version of a DHCPMsgType.
56func (o DHCPMsgType) String() string {
57	switch o {
58	case DHCPMsgTypeUnspecified:
59		return "Unspecified"
60	case DHCPMsgTypeDiscover:
61		return "Discover"
62	case DHCPMsgTypeOffer:
63		return "Offer"
64	case DHCPMsgTypeRequest:
65		return "Request"
66	case DHCPMsgTypeDecline:
67		return "Decline"
68	case DHCPMsgTypeAck:
69		return "Ack"
70	case DHCPMsgTypeNak:
71		return "Nak"
72	case DHCPMsgTypeRelease:
73		return "Release"
74	case DHCPMsgTypeInform:
75		return "Inform"
76	default:
77		return "Unknown"
78	}
79}
80
81//DHCPMagic is the RFC 2131 "magic cooke" for DHCP.
82var DHCPMagic uint32 = 0x63825363
83
84// DHCPv4 contains data for a single DHCP packet.
85type DHCPv4 struct {
86	BaseLayer
87	Operation    DHCPOp
88	HardwareType LinkType
89	HardwareLen  uint8
90	HardwareOpts uint8
91	Xid          uint32
92	Secs         uint16
93	Flags        uint16
94	ClientIP     net.IP
95	YourClientIP net.IP
96	NextServerIP net.IP
97	RelayAgentIP net.IP
98	ClientHWAddr net.HardwareAddr
99	ServerName   []byte
100	File         []byte
101	Options      DHCPOptions
102}
103
104// DHCPOptions is used to get nicely printed option lists which would normally
105// be cut off after 5 options.
106type DHCPOptions []DHCPOption
107
108// String returns a string version of the options list.
109func (o DHCPOptions) String() string {
110	buf := &bytes.Buffer{}
111	buf.WriteByte('[')
112	for i, opt := range o {
113		buf.WriteString(opt.String())
114		if i+1 != len(o) {
115			buf.WriteString(", ")
116		}
117	}
118	buf.WriteByte(']')
119	return buf.String()
120}
121
122// LayerType returns gopacket.LayerTypeDHCPv4
123func (d *DHCPv4) LayerType() gopacket.LayerType { return LayerTypeDHCPv4 }
124
125// DecodeFromBytes decodes the given bytes into this layer.
126func (d *DHCPv4) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
127	if len(data) < 240 {
128		df.SetTruncated()
129		return fmt.Errorf("DHCPv4 length %d too short", len(data))
130	}
131	d.Options = d.Options[:0]
132	d.Operation = DHCPOp(data[0])
133	d.HardwareType = LinkType(data[1])
134	d.HardwareLen = data[2]
135	d.HardwareOpts = data[3]
136	d.Xid = binary.BigEndian.Uint32(data[4:8])
137	d.Secs = binary.BigEndian.Uint16(data[8:10])
138	d.Flags = binary.BigEndian.Uint16(data[10:12])
139	d.ClientIP = net.IP(data[12:16])
140	d.YourClientIP = net.IP(data[16:20])
141	d.NextServerIP = net.IP(data[20:24])
142	d.RelayAgentIP = net.IP(data[24:28])
143	d.ClientHWAddr = net.HardwareAddr(data[28 : 28+d.HardwareLen])
144	d.ServerName = data[44:108]
145	d.File = data[108:236]
146	if binary.BigEndian.Uint32(data[236:240]) != DHCPMagic {
147		return InvalidMagicCookie
148	}
149
150	if len(data) <= 240 {
151		// DHCP Packet could have no option (??)
152		return nil
153	}
154
155	options := data[240:]
156
157	stop := len(options)
158	start := 0
159	for start < stop {
160		o := DHCPOption{}
161		if err := o.decode(options[start:]); err != nil {
162			return err
163		}
164		if o.Type == DHCPOptEnd {
165			break
166		}
167		d.Options = append(d.Options, o)
168		// Check if the option is a single byte pad
169		if o.Type == DHCPOptPad {
170			start++
171		} else {
172			start += int(o.Length) + 2
173		}
174	}
175
176	d.Contents = data
177
178	return nil
179}
180
181// Len returns the length of a DHCPv4 packet.
182func (d *DHCPv4) Len() uint16 {
183	n := uint16(240)
184	for _, o := range d.Options {
185		if o.Type == DHCPOptPad {
186			n++
187		} else {
188			n += uint16(o.Length) + 2
189		}
190	}
191	n++ // for opt end
192	return n
193}
194
195// SerializeTo writes the serialized form of this layer into the
196// SerializationBuffer, implementing gopacket.SerializableLayer.
197// See the docs for gopacket.SerializableLayer for more info.
198func (d *DHCPv4) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
199	plen := int(d.Len())
200
201	data, err := b.PrependBytes(plen)
202	if err != nil {
203		return err
204	}
205
206	data[0] = byte(d.Operation)
207	data[1] = byte(d.HardwareType)
208	if opts.FixLengths {
209		d.HardwareLen = uint8(len(d.ClientHWAddr))
210	}
211	data[2] = d.HardwareLen
212	data[3] = d.HardwareOpts
213	binary.BigEndian.PutUint32(data[4:8], d.Xid)
214	binary.BigEndian.PutUint16(data[8:10], d.Secs)
215	binary.BigEndian.PutUint16(data[10:12], d.Flags)
216	copy(data[12:16], d.ClientIP.To4())
217	copy(data[16:20], d.YourClientIP.To4())
218	copy(data[20:24], d.NextServerIP.To4())
219	copy(data[24:28], d.RelayAgentIP.To4())
220	copy(data[28:44], d.ClientHWAddr)
221	copy(data[44:108], d.ServerName)
222	copy(data[108:236], d.File)
223	binary.BigEndian.PutUint32(data[236:240], DHCPMagic)
224
225	if len(d.Options) > 0 {
226		offset := 240
227		for _, o := range d.Options {
228			if err := o.encode(data[offset:]); err != nil {
229				return err
230			}
231			// A pad option is only a single byte
232			if o.Type == DHCPOptPad {
233				offset++
234			} else {
235				offset += 2 + len(o.Data)
236			}
237		}
238		optend := NewDHCPOption(DHCPOptEnd, nil)
239		if err := optend.encode(data[offset:]); err != nil {
240			return err
241		}
242	}
243	return nil
244}
245
246// CanDecode returns the set of layer types that this DecodingLayer can decode.
247func (d *DHCPv4) CanDecode() gopacket.LayerClass {
248	return LayerTypeDHCPv4
249}
250
251// NextLayerType returns the layer type contained by this DecodingLayer.
252func (d *DHCPv4) NextLayerType() gopacket.LayerType {
253	return gopacket.LayerTypePayload
254}
255
256func decodeDHCPv4(data []byte, p gopacket.PacketBuilder) error {
257	dhcp := &DHCPv4{}
258	err := dhcp.DecodeFromBytes(data, p)
259	if err != nil {
260		return err
261	}
262	p.AddLayer(dhcp)
263	return p.NextDecoder(gopacket.LayerTypePayload)
264}
265
266// DHCPOpt represents a DHCP option or parameter from RFC-2132
267type DHCPOpt byte
268
269// Constants for the DHCPOpt options.
270const (
271	DHCPOptPad                   DHCPOpt = 0
272	DHCPOptSubnetMask            DHCPOpt = 1   // 4, net.IP
273	DHCPOptTimeOffset            DHCPOpt = 2   // 4, int32 (signed seconds from UTC)
274	DHCPOptRouter                DHCPOpt = 3   // n*4, [n]net.IP
275	DHCPOptTimeServer            DHCPOpt = 4   // n*4, [n]net.IP
276	DHCPOptNameServer            DHCPOpt = 5   // n*4, [n]net.IP
277	DHCPOptDNS                   DHCPOpt = 6   // n*4, [n]net.IP
278	DHCPOptLogServer             DHCPOpt = 7   // n*4, [n]net.IP
279	DHCPOptCookieServer          DHCPOpt = 8   // n*4, [n]net.IP
280	DHCPOptLPRServer             DHCPOpt = 9   // n*4, [n]net.IP
281	DHCPOptImpressServer         DHCPOpt = 10  // n*4, [n]net.IP
282	DHCPOptResLocServer          DHCPOpt = 11  // n*4, [n]net.IP
283	DHCPOptHostname              DHCPOpt = 12  // n, string
284	DHCPOptBootfileSize          DHCPOpt = 13  // 2, uint16
285	DHCPOptMeritDumpFile         DHCPOpt = 14  // >1, string
286	DHCPOptDomainName            DHCPOpt = 15  // n, string
287	DHCPOptSwapServer            DHCPOpt = 16  // n*4, [n]net.IP
288	DHCPOptRootPath              DHCPOpt = 17  // n, string
289	DHCPOptExtensionsPath        DHCPOpt = 18  // n, string
290	DHCPOptIPForwarding          DHCPOpt = 19  // 1, bool
291	DHCPOptSourceRouting         DHCPOpt = 20  // 1, bool
292	DHCPOptPolicyFilter          DHCPOpt = 21  // 8*n, [n]{net.IP/net.IP}
293	DHCPOptDatagramMTU           DHCPOpt = 22  // 2, uint16
294	DHCPOptDefaultTTL            DHCPOpt = 23  // 1, byte
295	DHCPOptPathMTUAgingTimeout   DHCPOpt = 24  // 4, uint32
296	DHCPOptPathPlateuTableOption DHCPOpt = 25  // 2*n, []uint16
297	DHCPOptInterfaceMTU          DHCPOpt = 26  // 2, uint16
298	DHCPOptAllSubsLocal          DHCPOpt = 27  // 1, bool
299	DHCPOptBroadcastAddr         DHCPOpt = 28  // 4, net.IP
300	DHCPOptMaskDiscovery         DHCPOpt = 29  // 1, bool
301	DHCPOptMaskSupplier          DHCPOpt = 30  // 1, bool
302	DHCPOptRouterDiscovery       DHCPOpt = 31  // 1, bool
303	DHCPOptSolicitAddr           DHCPOpt = 32  // 4, net.IP
304	DHCPOptStaticRoute           DHCPOpt = 33  // n*8, [n]{net.IP/net.IP} -- note the 2nd is router not mask
305	DHCPOptARPTrailers           DHCPOpt = 34  // 1, bool
306	DHCPOptARPTimeout            DHCPOpt = 35  // 4, uint32
307	DHCPOptEthernetEncap         DHCPOpt = 36  // 1, bool
308	DHCPOptTCPTTL                DHCPOpt = 37  // 1, byte
309	DHCPOptTCPKeepAliveInt       DHCPOpt = 38  // 4, uint32
310	DHCPOptTCPKeepAliveGarbage   DHCPOpt = 39  // 1, bool
311	DHCPOptNISDomain             DHCPOpt = 40  // n, string
312	DHCPOptNISServers            DHCPOpt = 41  // 4*n,  [n]net.IP
313	DHCPOptNTPServers            DHCPOpt = 42  // 4*n, [n]net.IP
314	DHCPOptVendorOption          DHCPOpt = 43  // n, [n]byte // may be encapsulated.
315	DHCPOptNetBIOSTCPNS          DHCPOpt = 44  // 4*n, [n]net.IP
316	DHCPOptNetBIOSTCPDDS         DHCPOpt = 45  // 4*n, [n]net.IP
317	DHCPOptNETBIOSTCPNodeType    DHCPOpt = 46  // 1, magic byte
318	DHCPOptNetBIOSTCPScope       DHCPOpt = 47  // n, string
319	DHCPOptXFontServer           DHCPOpt = 48  // n, string
320	DHCPOptXDisplayManager       DHCPOpt = 49  // n, string
321	DHCPOptRequestIP             DHCPOpt = 50  // 4, net.IP
322	DHCPOptLeaseTime             DHCPOpt = 51  // 4, uint32
323	DHCPOptExtOptions            DHCPOpt = 52  // 1, 1/2/3
324	DHCPOptMessageType           DHCPOpt = 53  // 1, 1-7
325	DHCPOptServerID              DHCPOpt = 54  // 4, net.IP
326	DHCPOptParamsRequest         DHCPOpt = 55  // n, []byte
327	DHCPOptMessage               DHCPOpt = 56  // n, 3
328	DHCPOptMaxMessageSize        DHCPOpt = 57  // 2, uint16
329	DHCPOptT1                    DHCPOpt = 58  // 4, uint32
330	DHCPOptT2                    DHCPOpt = 59  // 4, uint32
331	DHCPOptClassID               DHCPOpt = 60  // n, []byte
332	DHCPOptClientID              DHCPOpt = 61  // n >=  2, []byte
333	DHCPOptDomainSearch          DHCPOpt = 119 // n, string
334	DHCPOptSIPServers            DHCPOpt = 120 // n, url
335	DHCPOptClasslessStaticRoute  DHCPOpt = 121 //
336	DHCPOptEnd                   DHCPOpt = 255
337)
338
339// String returns a string version of a DHCPOpt.
340func (o DHCPOpt) String() string {
341	switch o {
342	case DHCPOptPad:
343		return "(padding)"
344	case DHCPOptSubnetMask:
345		return "SubnetMask"
346	case DHCPOptTimeOffset:
347		return "TimeOffset"
348	case DHCPOptRouter:
349		return "Router"
350	case DHCPOptTimeServer:
351		return "rfc868" // old time server protocol stringified to dissuade confusion w. NTP
352	case DHCPOptNameServer:
353		return "ien116" // obscure nameserver protocol stringified to dissuade confusion w. DNS
354	case DHCPOptDNS:
355		return "DNS"
356	case DHCPOptLogServer:
357		return "mitLCS" // MIT LCS server protocol yada yada w. Syslog
358	case DHCPOptCookieServer:
359		return "CookieServer"
360	case DHCPOptLPRServer:
361		return "LPRServer"
362	case DHCPOptImpressServer:
363		return "ImpressServer"
364	case DHCPOptResLocServer:
365		return "ResourceLocationServer"
366	case DHCPOptHostname:
367		return "Hostname"
368	case DHCPOptBootfileSize:
369		return "BootfileSize"
370	case DHCPOptMeritDumpFile:
371		return "MeritDumpFile"
372	case DHCPOptDomainName:
373		return "DomainName"
374	case DHCPOptSwapServer:
375		return "SwapServer"
376	case DHCPOptRootPath:
377		return "RootPath"
378	case DHCPOptExtensionsPath:
379		return "ExtensionsPath"
380	case DHCPOptIPForwarding:
381		return "IPForwarding"
382	case DHCPOptSourceRouting:
383		return "SourceRouting"
384	case DHCPOptPolicyFilter:
385		return "PolicyFilter"
386	case DHCPOptDatagramMTU:
387		return "DatagramMTU"
388	case DHCPOptDefaultTTL:
389		return "DefaultTTL"
390	case DHCPOptPathMTUAgingTimeout:
391		return "PathMTUAgingTimeout"
392	case DHCPOptPathPlateuTableOption:
393		return "PathPlateuTableOption"
394	case DHCPOptInterfaceMTU:
395		return "InterfaceMTU"
396	case DHCPOptAllSubsLocal:
397		return "AllSubsLocal"
398	case DHCPOptBroadcastAddr:
399		return "BroadcastAddress"
400	case DHCPOptMaskDiscovery:
401		return "MaskDiscovery"
402	case DHCPOptMaskSupplier:
403		return "MaskSupplier"
404	case DHCPOptRouterDiscovery:
405		return "RouterDiscovery"
406	case DHCPOptSolicitAddr:
407		return "SolicitAddr"
408	case DHCPOptStaticRoute:
409		return "StaticRoute"
410	case DHCPOptARPTrailers:
411		return "ARPTrailers"
412	case DHCPOptARPTimeout:
413		return "ARPTimeout"
414	case DHCPOptEthernetEncap:
415		return "EthernetEncap"
416	case DHCPOptTCPTTL:
417		return "TCPTTL"
418	case DHCPOptTCPKeepAliveInt:
419		return "TCPKeepAliveInt"
420	case DHCPOptTCPKeepAliveGarbage:
421		return "TCPKeepAliveGarbage"
422	case DHCPOptNISDomain:
423		return "NISDomain"
424	case DHCPOptNISServers:
425		return "NISServers"
426	case DHCPOptNTPServers:
427		return "NTPServers"
428	case DHCPOptVendorOption:
429		return "VendorOption"
430	case DHCPOptNetBIOSTCPNS:
431		return "NetBIOSOverTCPNS"
432	case DHCPOptNetBIOSTCPDDS:
433		return "NetBiosOverTCPDDS"
434	case DHCPOptNETBIOSTCPNodeType:
435		return "NetBIOSOverTCPNodeType"
436	case DHCPOptNetBIOSTCPScope:
437		return "NetBIOSOverTCPScope"
438	case DHCPOptXFontServer:
439		return "XFontServer"
440	case DHCPOptXDisplayManager:
441		return "XDisplayManager"
442	case DHCPOptEnd:
443		return "(end)"
444	case DHCPOptSIPServers:
445		return "SipServers"
446	case DHCPOptRequestIP:
447		return "RequestIP"
448	case DHCPOptLeaseTime:
449		return "LeaseTime"
450	case DHCPOptExtOptions:
451		return "ExtOpts"
452	case DHCPOptMessageType:
453		return "MessageType"
454	case DHCPOptServerID:
455		return "ServerID"
456	case DHCPOptParamsRequest:
457		return "ParamsRequest"
458	case DHCPOptMessage:
459		return "Message"
460	case DHCPOptMaxMessageSize:
461		return "MaxDHCPSize"
462	case DHCPOptT1:
463		return "Timer1"
464	case DHCPOptT2:
465		return "Timer2"
466	case DHCPOptClassID:
467		return "ClassID"
468	case DHCPOptClientID:
469		return "ClientID"
470	case DHCPOptDomainSearch:
471		return "DomainSearch"
472	case DHCPOptClasslessStaticRoute:
473		return "ClasslessStaticRoute"
474	default:
475		return "Unknown"
476	}
477}
478
479// DHCPOption rerpresents a DHCP option.
480type DHCPOption struct {
481	Type   DHCPOpt
482	Length uint8
483	Data   []byte
484}
485
486// String returns a string version of a DHCP Option.
487func (o DHCPOption) String() string {
488	switch o.Type {
489
490	case DHCPOptHostname, DHCPOptMeritDumpFile, DHCPOptDomainName, DHCPOptRootPath,
491		DHCPOptExtensionsPath, DHCPOptNISDomain, DHCPOptNetBIOSTCPScope, DHCPOptXFontServer,
492		DHCPOptXDisplayManager, DHCPOptMessage, DHCPOptDomainSearch: // string
493		return fmt.Sprintf("Option(%s:%s)", o.Type, string(o.Data))
494
495	case DHCPOptMessageType:
496		if len(o.Data) != 1 {
497			return fmt.Sprintf("Option(%s:INVALID)", o.Type)
498		}
499		return fmt.Sprintf("Option(%s:%s)", o.Type, DHCPMsgType(o.Data[0]))
500
501	case DHCPOptSubnetMask, DHCPOptServerID, DHCPOptBroadcastAddr,
502		DHCPOptSolicitAddr, DHCPOptRequestIP: // net.IP
503		if len(o.Data) < 4 {
504			return fmt.Sprintf("Option(%s:INVALID)", o.Type)
505		}
506		return fmt.Sprintf("Option(%s:%s)", o.Type, net.IP(o.Data))
507
508	case DHCPOptT1, DHCPOptT2, DHCPOptLeaseTime, DHCPOptPathMTUAgingTimeout,
509		DHCPOptARPTimeout, DHCPOptTCPKeepAliveInt: // uint32
510		if len(o.Data) != 4 {
511			return fmt.Sprintf("Option(%s:INVALID)", o.Type)
512		}
513		return fmt.Sprintf("Option(%s:%d)", o.Type,
514			uint32(o.Data[0])<<24|uint32(o.Data[1])<<16|uint32(o.Data[2])<<8|uint32(o.Data[3]))
515
516	case DHCPOptParamsRequest:
517		buf := &bytes.Buffer{}
518		buf.WriteString(fmt.Sprintf("Option(%s:", o.Type))
519		for i, v := range o.Data {
520			buf.WriteString(DHCPOpt(v).String())
521			if i+1 != len(o.Data) {
522				buf.WriteByte(',')
523			}
524		}
525		buf.WriteString(")")
526		return buf.String()
527
528	default:
529		return fmt.Sprintf("Option(%s:%v)", o.Type, o.Data)
530	}
531}
532
533// NewDHCPOption constructs a new DHCPOption with a given type and data.
534func NewDHCPOption(t DHCPOpt, data []byte) DHCPOption {
535	o := DHCPOption{Type: t}
536	if data != nil {
537		o.Data = data
538		o.Length = uint8(len(data))
539	}
540	return o
541}
542
543func (o *DHCPOption) encode(b []byte) error {
544	switch o.Type {
545	case DHCPOptPad, DHCPOptEnd:
546		b[0] = byte(o.Type)
547	default:
548		b[0] = byte(o.Type)
549		b[1] = o.Length
550		copy(b[2:], o.Data)
551	}
552	return nil
553}
554
555func (o *DHCPOption) decode(data []byte) error {
556	if len(data) < 1 {
557		// Pad/End have a length of 1
558		return DecOptionNotEnoughData
559	}
560	o.Type = DHCPOpt(data[0])
561	switch o.Type {
562	case DHCPOptPad, DHCPOptEnd:
563		o.Data = nil
564	default:
565		if len(data) < 2 {
566			return DecOptionNotEnoughData
567		}
568		o.Length = data[1]
569		if int(o.Length) > len(data[2:]) {
570			return DecOptionMalformed
571		}
572		o.Data = data[2 : 2+int(o.Length)]
573	}
574	return nil
575}
576
577// DHCPv4Error is used for constant errors for DHCPv4. It is needed for test asserts.
578type DHCPv4Error string
579
580// DHCPv4Error implements error interface.
581func (d DHCPv4Error) Error() string {
582	return string(d)
583}
584
585const (
586	// DecOptionNotEnoughData is returned when there is not enough data during option's decode process
587	DecOptionNotEnoughData = DHCPv4Error("Not enough data to decode")
588	// DecOptionMalformed is returned when the option is malformed
589	DecOptionMalformed = DHCPv4Error("Option is malformed")
590	// InvalidMagicCookie is returned when Magic cookie is missing into BOOTP header
591	InvalidMagicCookie = DHCPv4Error("Bad DHCP header")
592)
593