forked from ncw/go-nflog-acctd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nflog.go
276 lines (248 loc) · 7.12 KB
/
nflog.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
// Use cgo to interface with nflog
//
// Docs: http://www.netfilter.org/projects/libnetfilter_log/doxygen/index.html
//
// Debian packages needed:
// apt-get install iptables-dev linux-libc-dev libnetfilter-log-dev
package main
import (
"log"
"net"
"reflect"
"syscall"
"unsafe"
)
/*
#cgo LDFLAGS: -lnetfilter_log
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <libnetfilter_log/libnetfilter_log.h>
// Forward definition of Go function
void processPacket(void *, u_int32_t, int, void *);
// Process the incoming packet, handing it back to Go as soon as possible
static int _processPacket(struct nflog_g_handle *gh, struct nfgenmsg *nfmsg, struct nflog_data *nfd, void *data) {
char *payload = 0;
int payload_len = nflog_get_payload(nfd, &payload);
u_int32_t seq = 0;
nflog_get_seq(nfd, &seq);
processPacket(data, seq, payload_len, payload);
return 0;
}
// Register the callback - can't be done from Go
//
// We have to register a C function _processPacket which is a thin
// shim, calling the Go function as soon as possible
static int _callback_register(struct nflog_g_handle *gh, void *data) {
return nflog_callback_register(gh, _processPacket, data);
}
*/
import "C"
const (
RecvBufferSize = 4 * 1024 * 1024
NflogBufferSize = 4 * 1024 * 1024
MaxQueueLogs = 1024 * 1024
// NflogTimeout = 1024 // what unit?
)
// NfLog
type NfLog struct {
// Main nflog_handle
h *C.struct_nflog_handle
// File descriptor for socket operations
fd C.int
// Group handle
gh *C.struct_nflog_g_handle
// The multicast address
McastGroup int
// The next expected sequence number
seq uint32
// Errors
errors int64
// Flavour of IP we are expecting, 4 or 6
IpVersion byte
// Mask for the IP
Mask net.IPMask
// Whether to apply the mask or not
UseMask bool
// Are we account the source or the destination address
Direction IpDirection
// Flavour of IP packet we are decoding
IpPacket *IpPacketInfo
// Accounting
a *Accounting
// Quit the loop
quit bool
// Buffer for accumulated packets
addPackets []AddPacket
}
// Create a new NfLog
//
// McastGroup is that specified in ip[6]tables
// IPv6 is a flag to say if it is IPv6 or not
// Direction is to monitor the source address or the dest address
func NewNfLog(McastGroup int, IpVersion byte, Direction IpDirection, MaskBits int, a *Accounting) *NfLog {
h := C.nflog_open()
if h == nil {
log.Fatalf("Failed to open NFLOG: %s", strerror())
}
if *Debug {
log.Println("Binding nfnetlink_log to AF_INET")
}
if C.nflog_bind_pf(h, C.AF_INET) < 0 {
log.Fatalf("nflog_bind_pf failed: %s", strerror())
}
nflog := &NfLog{
h: h,
fd: C.nflog_fd(h),
McastGroup: McastGroup,
IpVersion: IpVersion,
Direction: Direction,
a: a,
}
switch IpVersion {
case 4:
nflog.IpPacket = Ip4Packet
case 6:
nflog.IpPacket = Ip6Packet
default:
log.Fatalf("Bad IP version %d", IpVersion)
}
addrBits := 8 * nflog.IpPacket.AddrLen
nflog.UseMask = MaskBits < addrBits
nflog.Mask = net.CIDRMask(MaskBits, addrBits)
nflog.makeGroup(McastGroup, nflog.IpPacket.HeaderSize)
// Start the background process
go nflog.Loop()
return nflog
}
// Receive data from nflog on a callback from C
//
//export processPacket
func processPacket(_nflog unsafe.Pointer, seq uint32, payload_len C.int, payload unsafe.Pointer) {
nflog := (*NfLog)(_nflog)
// Get the packet into a []byte
// NB if the C data goes away then BAD things will happen!
// So don't keep slices from this after returning from this function
var packet []byte
sliceHeader := (*reflect.SliceHeader)((unsafe.Pointer(&packet)))
sliceHeader.Cap = int(payload_len)
sliceHeader.Len = int(payload_len)
sliceHeader.Data = uintptr(payload)
// Call a standard Go method now
nflog.ProcessPacket(packet, seq)
}
// Process a packet
func (nflog *NfLog) ProcessPacket(packet []byte, seq uint32) {
// log.Printf("Packet %#v", packet)
// Peek the IP Version out of the header
ip_version := packet[IpVersion] >> IpVersionShift & IpVersionMask
// log.Printf("Received %d: size %d, IPv%d", seq, payload_len, ip_version)
if seq != 0 && seq != nflog.seq {
nflog.errors++
log.Printf("%d missing packets detected, %d to %d", seq-nflog.seq, seq, nflog.seq)
}
nflog.seq = seq + 1
if ip_version != nflog.IpVersion {
nflog.errors++
log.Printf("Bad IP version: %d", ip_version)
return
}
i := nflog.IpPacket
if len(packet) < i.HeaderSize {
nflog.errors++
log.Printf("Short IPv%s packet %d/%d bytes", ip_version, len(packet), i.HeaderSize)
return
}
var addr net.IP
if nflog.Direction {
addr = i.Src(packet)
} else {
addr = i.Dst(packet)
}
// Mask the address
if nflog.UseMask {
addr = addr.Mask(nflog.Mask)
}
nflog.addPackets = append(nflog.addPackets, AddPacket{
Direction: nflog.Direction,
Addr: string(addr),
Length: i.Length(packet),
})
}
// Current nflog error
func strerror() error {
return syscall.Errno(C.nflog_errno)
}
// Connects to the group specified with the size
func (nflog *NfLog) makeGroup(group, size int) {
if *Debug {
log.Printf("Binding this socket to group %d", group)
}
gh := C.nflog_bind_group(nflog.h, (C.u_int16_t)(group))
if gh == nil {
log.Fatalf("nflog_bind_group failed: %s", strerror())
}
nflog.gh = gh
// Set the maximum amount of logs in buffer for this group
if C.nflog_set_qthresh(gh, MaxQueueLogs) < 0 {
log.Fatalf("nflog_set_qthresh failed: %s", strerror())
}
// Set local sequence numbering to detect missing packets
if C.nflog_set_flags(gh, C.NFULNL_CFG_F_SEQ) < 0 {
log.Fatalf("nflog_set_flags failed: %s", strerror())
}
// Set buffer size large
if C.nflog_set_nlbufsiz(gh, NflogBufferSize) < 0 {
log.Fatalf("nflog_set_nlbufsiz: %s", strerror())
}
// Set timeout
// Doesn't seem to make any difference and don't know the unit
// if C.nflog_set_timeout(gh, NflogTimeout) < 0 {
// log.Fatalf("nflog_set_timeout: %s", strerror())
// }
if *Debug {
log.Printf("Setting copy_packet mode to %d bytes", size)
}
if C.nflog_set_mode(gh, C.NFULNL_COPY_PACKET, (C.uint)(size)) < 0 {
log.Fatalf("nflog_set_mode failed: %s", strerror())
}
// Register the callback now we are set up
C._callback_register(gh, unsafe.Pointer(nflog))
}
// Receive packets in a loop until quit
func (nflog *NfLog) Loop() {
buf := make([]byte, RecvBufferSize)
pbuf := unsafe.Pointer(&buf[0])
buflen := C.size_t(len(buf))
for !nflog.quit {
nr := C.recv(nflog.fd, pbuf, buflen, 0)
if nr < 0 {
log.Printf("Recvfrom failed: %s", strerror())
nflog.errors++
} else {
// Handle messages in packet reusing memory
ps := <-nflog.a.returnAddPackets
nflog.addPackets = ps[:0]
C.nflog_handle_packet(nflog.h, (*C.char)(pbuf), (C.int)(nr))
nflog.a.processAddPackets <- nflog.addPackets
nflog.addPackets = nil
}
}
}
// Close the NfLog down
func (nflog *NfLog) Close() {
if *Debug {
log.Printf("Unbinding this socket (%d) from group %d", nflog.fd, nflog.McastGroup)
}
nflog.quit = true
if C.nflog_unbind_group(nflog.gh) < 0 {
log.Printf("nflog_unbind_group(%d) failed: %s", nflog.McastGroup, strerror())
}
if *Debug {
log.Printf("Closing nflog")
}
if C.nflog_close(nflog.h) < 0 {
log.Printf("nflog_close failed: %s", strerror())
}
}