-
Notifications
You must be signed in to change notification settings - Fork 8
/
packet.go
167 lines (153 loc) · 4.99 KB
/
packet.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
// Copyright 2018-2019 Virta Laboratories, Inc. All rights reserved.
/*
Packet-handling nitty gritty.
*/
package main
import (
"encoding/json"
"fmt"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
// decodeLayers extracts information from packets and stuffs any discovered
// metadata into the provided Asset object.
func decodeLayers(packet gopacket.Packet, asset *Asset) error {
// Decode link, network, and transport layers to extract metadata about a
// packet that may represent an asset.
//
// Ignore errors produced by DecodeLayer() because we can still get
// information from the layers that didn't produce an error.
//
// Docs:
// https://godoc.org/github.com/google/gopacket#hdr-Fast_Decoding_With_DecodingLayerParser
var eth layers.Ethernet
var ip4 layers.IPv4
var ip6 layers.IPv6
var tcp layers.TCP
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ð, &ip4, &ip6, &tcp)
decoded := []gopacket.LayerType{}
logger.Println("Decode packet")
parser.DecodeLayers(packet.Data(), &decoded)
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeEthernet:
asset.MACAddress = eth.SrcMAC.String()
stats.AddLayer("Ethernet")
logger.Println(" Eth", eth.SrcMAC, eth.DstMAC)
case layers.LayerTypeIPv4:
asset.IPv4Address = ip4.SrcIP.String()
stats.AddLayer("IPv4")
logger.Println(" IP4", ip4.SrcIP, ip4.DstIP)
case layers.LayerTypeIPv6:
asset.IPv6Address = ip6.SrcIP.String()
stats.AddLayer("IPv6")
logger.Println(" IP6", ip6.SrcIP, ip6.DstIP)
case layers.LayerTypeTCP:
logger.Printf("TCP %d->%d seq %d\n", tcp.SrcPort, tcp.DstPort, tcp.Seq)
stats.AddLayer("TCP")
if tcp.SYN {
// If this packet has SYN+ACK, then the endpoint is accepting a
// connection on the *source* port (i.e., it's the "server" side
// of a new connection). If this packet has SYN but not ACK,
// then it is *initiating* a connection to the *destination*
// port (i.e., it's the "client" side of a new connection).
if tcp.ACK {
asset.ListensOnPort = tcp.SrcPort.String()
asset.Provenance = "TCP handshake"
stats.AddLayer("TCP/handshake")
logger.Printf(" TCP server on %s\n", tcp.SrcPort)
} else {
asset.ConnectsToPort = tcp.DstPort.String()
asset.Provenance = "TCP handshake"
stats.AddLayer("TCP/handshake")
logger.Printf(" TCP client to :%s\n", tcp.DstPort)
}
}
}
}
return nil
}
// parseApplicationLayer extracts information from a packet's application layer,
// if one exists, and updates a provided Asset object.
func parseApplicationLayer(packet gopacket.Packet, decoders []PayloadDecoder, asset *Asset) error {
app := packet.ApplicationLayer()
if app == nil {
return fmt.Errorf("No application layer")
}
// Update statstics
stats.AddLayer("Application")
// Try to decode the application layer using each available decoder in turn,
// stopping when a decoder succeeds or there are no decoders remaining.
for _, decoder := range decoders {
decoderName := decoder.Name()
identifier, provenance, err := decoder.DecodePayload(&app)
if err == nil {
// Success, we're done
asset.Identifier = identifier
asset.Provenance = provenance
stats.AddLayer("Application/" + decoderName)
break
}
}
if asset.Identifier == "" {
return fmt.Errorf("failed to find a decoder, no identifier")
}
return nil
}
// handlePacket extracts information from packets, invokes decoding functions
// that attempt to interpret the contents of application layers, updates
// packet-processing statistics, and optionally uploads its findings to a REST
// API endpoint.
func handlePacket(
packet gopacket.Packet,
appLayerDecoders []PayloadDecoder,
apiClient *APIClient,
assetCSVWriter *AssetCSVWriter,
waitGroup *sync.WaitGroup,
) {
if waitGroup != nil {
defer waitGroup.Done()
}
// Initialize an empty Asset to store information learned during dissection
asset := &Asset{}
asset.LastSeen = time.Now()
// Decode packet and update statistics
stats.AddPacket()
if err := decodeLayers(packet, asset); err != nil {
stats.AddError(err)
return
} else if err := parseApplicationLayer(packet, appLayerDecoders, asset); err != nil {
stats.AddError(err)
return
} else {
stats.AddAsset(asset)
}
// Write to stdout and stderr
bytesRepresentation, err := json.Marshal(asset)
if err != nil {
stats.AddError(err)
}
stringRepresentation := string(bytesRepresentation)
if verbose {
fmt.Println(stringRepresentation)
}
logger.Println(stringRepresentation)
// Write to CSV file if requested by the user.
if assetCSVWriter != nil && assetCSVWriter.Enabled() {
if err := assetCSVWriter.Append(asset); err != nil {
stats.AddError(err)
}
}
// Upload to API if requested by the user. If the user did not specify a
// URL with a command line flag, the URL will be empty.
if apiClient.enabled {
if _, err := apiClient.Upload(asset); err != nil {
logger.Println("API Upload error:", err)
stats.AddUploadError(err)
} else {
stats.AddUpload()
}
}
}