-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlayer_frame.go
465 lines (426 loc) · 15.6 KB
/
layer_frame.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
package packet
import (
"bytes"
"net"
"net/netip"
"sync/atomic"
"syscall"
"time"
"github.com/irai/packet/fastlog"
)
//go:generate stringer -type=PayloadID
type PayloadID int
const (
PayloadEther PayloadID = 1
Payload8023 PayloadID = 2
PayloadARP PayloadID = 3
PayloadIP4 PayloadID = 4
PayloadIP6 PayloadID = 5
PayloadICMP4 PayloadID = 6
PayloadICMP6 PayloadID = 7
PayloadUDP PayloadID = 8
PayloadTCP PayloadID = 9
PayloadDHCP4 PayloadID = 10
PayloadDHCP6 PayloadID = 11
PayloadDNS PayloadID = 12
PayloadMDNS PayloadID = 13
PayloadSSL PayloadID = 14
PayloadNTP PayloadID = 15
PayloadSSDP PayloadID = 16
PayloadWSDP PayloadID = 17
PayloadNBNS PayloadID = 18
PayloadPlex PayloadID = 19
PayloadUbiquiti PayloadID = 20
PayloadLLMNR PayloadID = 21
PayloadIGMP PayloadID = 22
PayloadEthernetPause PayloadID = 23
PayloadRRCP PayloadID = 24
PayloadLLDP PayloadID = 25
Payload802_11r PayloadID = 26
PayloadIEEE1905 PayloadID = 27
PayloadSonos PayloadID = 28
Payload880a PayloadID = 29
)
// Frame describes a network packet and the various protocol layers within it.
// It maintains a reference to common protocols like IP4, IP6, UDP, TCP.
type Frame struct {
ether Ether // reference the full packet
offsetIP4 int // offset to IP4 packet
offsetIP6 int // offset to IP6 packet
offsetUDP int // offset to UDP packet
offsetTCP int // offset to TCP packet
offsetPayload int // offset to rest of payload
PayloadID PayloadID // protocol ID for value in payload
SrcAddr Addr // reference to source IP, MAC and Port number (if available)
DstAddr Addr // reference to destination IP, MAC and Port number (if available)
Session *Session // session where frame was capture
Host *Host // pointer to Host entry for this IP address
flags uint // processing flags : online_transition (0x01), offline_transition (0x02)
}
func (frame Frame) onlineTransition() bool { return frame.flags&0x01 == 0x01 }
func (frame Frame) markOnlineTransition() uint { return frame.flags | 0b01 }
func (frame Frame) offlineTransition() bool { return frame.flags&0x02 == 0x02 }
func (frame Frame) setOfflineTransition() uint { return frame.flags | 0b10 }
func (frame Frame) Log(line *fastlog.Line) *fastlog.Line {
line.String("payloadID", frame.PayloadID.String())
line.MAC("srcMAC", frame.SrcAddr.MAC)
line.IP("srcIP", frame.SrcAddr.IP)
line.MAC("dstMAC", frame.DstAddr.MAC)
line.IP("dstIP", frame.DstAddr.IP)
line.Int("payloadlen", len(frame.Payload()))
if frame.Host != nil {
line.Bool("captured", frame.Host.MACEntry.Captured)
}
return line
}
func (f Frame) Ether() Ether {
return f.ether
}
// HasIP returns true if the packet contains either an IPv4 or IPv6 frame.
func (f Frame) HasIP() bool {
return (f.offsetIP4 != 0 || f.offsetIP6 != 0)
}
// IP4 returns a reference to the IP4 packet or nil if this is not an IPv4 packet.
func (f Frame) IP4() IP4 {
if f.offsetIP4 != 0 {
return IP4(f.ether[f.offsetIP4:])
}
return nil
}
// IP6 returns a reference to the IP6 packet or nil if this is not an IPv6 packet.
func (f Frame) IP6() IP6 {
if f.offsetIP6 != 0 {
return IP6(f.ether[f.offsetIP6:])
}
return nil
}
// UDP returns a reference to the UDP packet or nil if this is not a UDP packet.
func (f Frame) UDP() UDP {
if f.offsetUDP != 0 {
return UDP(f.ether[f.offsetUDP:])
}
return nil
}
// TCP returns a reference to the TCP packet or nil if this is not a TCP packet.
func (f Frame) TCP() TCP {
if f.offsetTCP != 0 {
return TCP(f.ether[f.offsetTCP:])
}
return nil
}
// Payload retuns a reference to the last payload in the envelope. This is
// typically the application layer protocol in a UDP or TCP packet.
// Payload will always contain the last payload processed without errors.
// In case of protocol validation errors the Payload will return the last valid payload.
func (f Frame) Payload() []byte {
if f.offsetPayload != 0 {
return f.ether[f.offsetPayload:]
}
return nil
}
type ProtoStats struct {
Proto PayloadID
Count int
ErrCount int
Last time.Time
}
// Parse returns a Frame containing references to common layers and the payload. It will also
// create the host entry if this is a new IP. The function is fast as it
// will map to the underlying array. No copy and no allocation takes place.
//
// Benchmark result: Jan 2021
// cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
// Benchmark_Parse-8
// 25281475 47.58 ns/op 0 B/op 0 allocs/op
func (h *Session) Parse(p []byte) (frame Frame, err error) {
frame.ether = p
if err := frame.ether.IsValid(); err != nil {
return Frame{}, err
}
frame.Session = h
frame.SrcAddr.MAC = frame.ether.Src()
frame.DstAddr.MAC = frame.ether.Dst()
frame.PayloadID = PayloadEther
frame.offsetPayload = frame.ether.HeaderLen()
// Only interested in unicast ethernet
if !IsUnicastMAC(frame.SrcAddr.MAC) {
return frame, nil
}
// In order to allow Ethernet II and IEEE 802.3 framing to be used on the same Ethernet segment,
// a unifying standard, IEEE 802.3x-1997, was introduced that required that EtherType values be greater than or equal to 1536.
// Thus, values of 1500 and below for this field indicate that the field is used as the size of the payload of the Ethernet frame
// while values of 1536 and above indicate that the field is used to represent an EtherType.
if frame.ether.EtherType() < 1536 {
frame.PayloadID = Payload8023
return frame, nil
}
var proto uint8
switch frame.ether.EtherType() {
case syscall.ETH_P_IP:
frame.PayloadID = PayloadIP4
ip4 := IP4(frame.Payload())
if err := ip4.IsValid(); err != nil {
return frame, err
}
atomic.StoreUint32(&h.ipHeartBeat, 1)
h.Statistics[PayloadIP4].Count++
frame.offsetIP4 = frame.offsetPayload
frame.offsetPayload = frame.offsetPayload + ip4.IHL()
proto = ip4.Protocol()
frame.SrcAddr.IP = ip4.Src()
frame.DstAddr.IP = ip4.Dst()
// create host if ip is local lan IP (note that we may receive multicast and broadcast packets and should not create hosts for these)
// don't create host if packets sent via our interface.
// If we don't have this, then we received all sent and forwarded packets with client IPs containing our host mac
if !bytes.Equal(frame.SrcAddr.MAC, h.NICInfo.HostAddr4.MAC) && frame.Session.NICInfo.HomeLAN4.Contains(frame.SrcAddr.IP) {
frame.Host, _ = frame.Session.findOrCreateHostWithLock(frame.SrcAddr) // will lock/unlock
if !frame.Host.Online {
frame.Session.onlineTransition(frame.Host)
frame.flags = frame.markOnlineTransition()
}
}
case syscall.ETH_P_IPV6:
frame.PayloadID = PayloadIP6
ip6 := IP6(frame.Payload())
if err := ip6.IsValid(); err != nil {
return frame, err
}
atomic.StoreUint32(&h.ipHeartBeat, 1)
h.Statistics[PayloadIP6].Count++
proto = ip6.NextHeader()
frame.SrcAddr.IP = ip6.Src()
frame.DstAddr.IP = ip6.Dst()
frame.offsetIP6 = frame.offsetPayload
frame.offsetPayload = frame.offsetPayload + ip6.HeaderLen()
// create host if src IP is:
// - unicast local link address (i.e. fe80::)
// - global IP6 sent by a local host not the router
//
// We ignore IP6 packets forwarded by the router to a local host using a Global Unique Addresses.
// For example, an IP6 google search will be forwared by the router as:
// ip6 src=google.com dst=GUA localhost and srcMAC=routerMAC dstMAC=localHostMAC
// TODO: is it better to check if IP is in the prefix?
//
// don't create host if packets sent via our interface.
// If we don't have this, then we received all sent and forwarded packets with client IPs containing our host mac
if !bytes.Equal(frame.SrcAddr.MAC, h.NICInfo.HostAddr4.MAC) &&
(frame.SrcAddr.IP.IsLinkLocalUnicast() ||
(frame.SrcAddr.IP.IsGlobalUnicast() && !bytes.Equal(frame.SrcAddr.MAC, frame.Session.NICInfo.RouterAddr4.MAC))) {
frame.Host, _ = frame.Session.findOrCreateHostWithLock(frame.SrcAddr) // will lock/unlock
if !frame.Host.Online {
frame.Session.onlineTransition(frame.Host)
frame.flags = frame.markOnlineTransition()
}
}
case syscall.ETH_P_ARP:
frame.PayloadID = PayloadARP
var arp []byte
// hard code arp validation; we don't have access to ARP frame in this package
if arp = frame.Payload(); len(arp) < 28 && arp[4] != 6 {
return frame, ErrParseFrame
}
h.Statistics[PayloadARP].Count++
// create host if new IP appears in arp packet
// don't create host if packets sent via our interface.
// If we don't have this, then we received all sent and forwarded packets with client IPs containing our host mac
// Validates arp len and that hardware len is 6 for mac address
srcIP := netip.AddrFrom4(*((*[4]byte)(arp[14:18])))
if !bytes.Equal(frame.SrcAddr.MAC, h.NICInfo.HostAddr4.MAC) &&
frame.Session.NICInfo.HomeLAN4.Contains(srcIP) {
addr := Addr{MAC: net.HardwareAddr(arp[8:14]), IP: srcIP} // use arp src mac and ip for lookup
frame.Host, _ = frame.Session.findOrCreateHostWithLock(addr) // will lock/unlock
if !frame.Host.Online {
frame.Session.onlineTransition(frame.Host)
frame.flags = frame.markOnlineTransition()
}
}
return frame, nil
case 0x8808: // Ethernet pause frame
frame.PayloadID = PayloadEthernetPause
h.Statistics[PayloadEthernetPause].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x8899: // Realtek remote control protocol (RRCP)
frame.PayloadID = PayloadRRCP
h.Statistics[PayloadRRCP].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x88cc: // Local link discovery protocol (LLDP)
frame.PayloadID = PayloadLLDP
h.Statistics[PayloadLLDP].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x890d: // 802.11r
frame.PayloadID = Payload802_11r
h.Statistics[Payload802_11r].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x893a: // IEEE 1905
frame.PayloadID = PayloadIEEE1905
h.Statistics[PayloadIEEE1905].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x6970: // Sonos proprietary protocol
frame.PayloadID = PayloadSonos
h.Statistics[PayloadSonos].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
case 0x880a: // not sure what this is but seen often on home LANs
frame.PayloadID = Payload880a
h.Statistics[Payload880a].Count++
frame.offsetPayload = frame.Ether().HeaderLen()
return frame, nil
default:
return frame, nil
}
switch proto {
case syscall.IPPROTO_UDP:
frame.PayloadID = PayloadUDP
udp := UDP(frame.Payload())
if err := udp.IsValid(); err != nil {
return frame, err
}
h.Statistics[PayloadUDP].Count++
frame.offsetUDP = frame.offsetPayload
frame.SrcAddr.Port = udp.SrcPort()
frame.DstAddr.Port = udp.DstPort()
switch {
default:
return frame, nil
case frame.SrcAddr.Port == 443 || frame.DstAddr.Port == 443: // SSL
frame.PayloadID = PayloadSSL
h.Statistics[PayloadSSL].Count++
case frame.DstAddr.Port == 67 || frame.DstAddr.Port == 68: // DHCP4 packet
frame.PayloadID = PayloadDHCP4
h.Statistics[PayloadDHCP4].Count++
case frame.DstAddr.Port == 546 || frame.DstAddr.Port == 547: // DHCP6
frame.PayloadID = PayloadDHCP6
h.Statistics[PayloadDHCP6].Count++
case frame.SrcAddr.Port == 53 || frame.DstAddr.Port == 53: // DNS request
frame.PayloadID = PayloadDNS
h.Statistics[PayloadDNS].Count++
case frame.SrcAddr.Port == 5353 || frame.DstAddr.Port == 5353: // Multicast DNS (MDNS)
frame.PayloadID = PayloadMDNS
h.Statistics[PayloadMDNS].Count++
case frame.SrcAddr.Port == 5355 || frame.DstAddr.Port == 5355: // Link Local Multicast Name Resolution (LLMNR)
frame.PayloadID = PayloadLLMNR
h.Statistics[PayloadLLMNR].Count++
case frame.SrcAddr.Port == 123 || frame.DstAddr.Port == 123: // NTP
frame.PayloadID = PayloadNTP
h.Statistics[PayloadNTP].Count++
case frame.SrcAddr.Port == 1900 || frame.DstAddr.Port == 1900: // Microsoft Simple Service Discovery Protocol (SSDP)
frame.PayloadID = PayloadSSDP
h.Statistics[PayloadSSDP].Count++
case frame.SrcAddr.Port == 3702 || frame.DstAddr.Port == 3702: // Web Services Discovery Protocol (WSD)
frame.PayloadID = PayloadWSDP
h.Statistics[PayloadWSDP].Count++
case frame.DstAddr.Port == 137 || frame.DstAddr.Port == 138: // Netbions NBNS
frame.PayloadID = PayloadNBNS
h.Statistics[PayloadNBNS].Count++
case frame.DstAddr.Port == 32412 || frame.DstAddr.Port == 32414: // Plex application protocol
frame.PayloadID = PayloadPlex
h.Statistics[PayloadPlex].Count++
case frame.SrcAddr.Port == 10001 || frame.DstAddr.Port == 10001: // Ubiquiti device discovery protocol
frame.PayloadID = PayloadUbiquiti
h.Statistics[PayloadUbiquiti].Count++
}
frame.offsetPayload = frame.offsetPayload + udp.HeaderLen() // only update offset if known header
return frame, nil
case syscall.IPPROTO_TCP:
frame.PayloadID = PayloadTCP
tcp := TCP(frame.Payload())
if err := tcp.IsValid(); err != nil {
return frame, err
}
h.Statistics[PayloadTCP].Count++
frame.offsetTCP = frame.offsetPayload
frame.SrcAddr.Port = tcp.SrcPort()
frame.DstAddr.Port = tcp.DstPort()
return frame, nil
case syscall.IPPROTO_ICMP:
icmpFrame := ICMP(frame.Payload())
if err := icmpFrame.IsValid(); err != nil {
return frame, err
}
// process echo reply to unblock ping if running
if icmpFrame.Type() == ICMP4TypeEchoReply {
echo := ICMPEcho(icmpFrame)
if err := echo.IsValid(); err != nil {
return frame, err
}
echoNotify(echo.EchoID()) // unblock ping if waiting
}
frame.PayloadID = PayloadICMP4
h.Statistics[PayloadICMP4].Count++
return frame, nil
case syscall.IPPROTO_ICMPV6:
icmpFrame := ICMP(frame.Payload())
if err := icmpFrame.IsValid(); err != nil {
return frame, err
}
// process echo reply to unblock ping if running
if icmpFrame.Type() == ICMP6TypeEchoReply {
echo := ICMPEcho(icmpFrame)
if err := echo.IsValid(); err != nil {
return frame, err
}
echoNotify(echo.EchoID()) // unblock ping if waiting
}
frame.PayloadID = PayloadICMP6
h.Statistics[PayloadICMP6].Count++
return frame, nil
case syscall.IPPROTO_IGMP:
frame.PayloadID = PayloadIGMP
h.Statistics[PayloadIGMP].Count++
return frame, nil
}
return frame, nil
}
func (h *Session) onlineTransition(host *Host) {
if host.Online {
return
}
host.MACEntry.Online = true
host.Online = true
host.dirty = true
var line *fastlog.Line
if Logger.IsInfo() {
line = Logger.Msg("IP is online").Struct(host.Addr)
}
if host.Addr.IP.Is4() {
if host.Addr.IP != host.MACEntry.IP4 { // changed IP4
if line != nil {
line.IP("previous", host.MACEntry.IP4)
}
host.MACEntry.IP4 = host.Addr.IP
for _, v := range host.MACEntry.HostList {
if v.Addr.IP.Is4() && v.Addr.IP != host.Addr.IP {
if v.Online {
if Logger.IsInfo() {
Logger.Msg("IP is offline").Struct(v.Addr).Write()
}
v.Online = false
v.dirty = true
}
}
}
}
} else {
if host.Addr.IP.IsGlobalUnicast() && host.Addr.IP != host.MACEntry.IP6GUA { // changed IP6 global unique address
if line != nil {
line.IP("previous", host.MACEntry.IP6GUA)
}
host.MACEntry.IP6GUA = host.Addr.IP
}
if host.Addr.IP.IsLinkLocalUnicast() && host.Addr.IP != host.MACEntry.IP6LLA { // changed IP6 link local address
if line != nil {
line.IP("previous", host.MACEntry.IP6LLA)
}
host.MACEntry.IP6LLA = host.Addr.IP
// don't set offline IP as we don't target LLA
}
}
if line != nil {
line.Write()
}
}