-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathl7-queue.cpp
More file actions
303 lines (244 loc) · 8.99 KB
/
l7-queue.cpp
File metadata and controls
303 lines (244 loc) · 8.99 KB
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
/*
The l7-queue class handles libnetfilter-queue events and passes
packets to their appropriate conntack for classification.
By Ethan Sommer <sommere@users.sf.net> and Matthew Strait
<quadong@users.sf.net>, 2006-2007
http://l7-filter.sf.net
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version
2 of the License, or (at your option) any later version.
http://www.gnu.org/licenses/gpl.txt
Based on nfqnl_test.c from libnetfilter-queue 0.0.12
If you get error messages about running out of buffer space, increase it
with something like:
echo 524280 > /proc/sys/net/core/rmem_default
echo 524280 > /proc/sys/net/core/rmem_max
echo 524280 > /proc/sys/net/core/wmem_default
echo 524280 > /proc/sys/net/core/wmem_max
*/
using namespace std;
#include <pthread.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <map>
#include <netinet/in.h>
#include <linux/types.h>
#include <cstring>
#include "l7-conntrack.h"
#include "l7-queue.h"
#include "util.h"
// Probably shouldn't really be global, but it's SO much easier
int maxpackets = 10; // by default.
int clobbermark = 0;
extern unsigned int markmask;
extern unsigned int maskfirstbit;
extern "C" {
#include <linux/netfilter.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
}
static int l7_queue_cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg,
struct nfq_data *nfa, void *data)
{
struct nfqnl_msg_packet_hdr *ph;
u_int32_t id = 0;
ph = nfq_get_msg_packet_hdr(nfa);
if(ph)
id = ntohl(ph->packet_id);
u_int32_t wholemark = nfq_get_nfmark(nfa);
// If it already has a mark (and we don't want to clobber it),
// just pass it back with the same mark
if((wholemark<<maskfirstbit)&markmask != UNTOUCHED && !clobbermark){
static unsigned int naaltered = 0;
naaltered++;
if((naaltered^(naaltered-1)) == (2*naaltered-1)) // is it a power of 2?
cerr << "My part of the mark has already been altered, ignoring these "
"packets!\n(" << naaltered << " ignored so far.) "
"Fix your rules or use l7-filter -c.\n";
return nfq_set_verdict_mark(qh, id, NF_ACCEPT, htonl(wholemark), 0, NULL);
}
return ((l7_queue *)data)->handle_packet(nfa, qh);
}
l7_queue::l7_queue(l7_conntrack *connection_tracker)
{
l7_connection_tracker = connection_tracker;
}
l7_queue::~l7_queue()
{
}
void l7_queue::start(int queuenum)
{
struct nfq_handle *h;
struct nfq_q_handle *qh;
struct nfnl_handle *nh;
int fd;
int rv;
char buf[4096];
l7printf(3, "opening library handle\n");
h = nfq_open();
if(!h) {
cerr << "error during nfq_open()\n";
exit(1);
}
l7printf(3, "unbinding existing nf_queue handler for AF_INET (if any)\n");
/* As per Patrick McHardy's suggestion at
http://www.spinics.net/lists/netfilter/msg42063.html
we, for now, ignore the return value of nfq_unbind_pf() */
if(nfq_unbind_pf(h, AF_INET) < 0 && 0) {
cerr << "error during nfq_unbind_pf()\n";
exit(1);
}
l7printf(3, "binding nfnetlink_queue as nf_queue handler for AF_INET\n");
if(nfq_bind_pf(h, AF_INET) < 0) {
cerr << "error during nfq_bind_pf()\n";
exit(1);
}
l7printf(3, "binding this socket to queue '0'\n");
qh = nfq_create_queue(h, queuenum, &l7_queue_cb, this);
if(!qh) {
cerr << "error during nfq_create_queue()\n";
exit(1);
}
l7printf(3, "setting copy_packet mode\n");
if(nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
cerr << "can't set packet_copy mode\n";
exit(1);
}
nh = nfq_nfnlh(h);
fd = nfnl_fd(nh);
// this is the main loop
while (true){
while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0)
nfq_handle_packet(h, buf, rv);
cerr << "Error: recv() returned negative value." << endl;
cerr << "rv=" << rv << endl;
cerr << "errno=" << errno << endl;
cerr << "errstr=" << strerror(errno) << endl << endl;
}
l7printf(3, "unbinding from queue 0\n");
nfq_destroy_queue(qh);
l7printf(3, "closing library handle\n");
nfq_close(h);
exit(0);
}
u_int32_t l7_queue::handle_packet(nfq_data * tb, struct nfq_q_handle *qh)
{
int id = 0, ret, dataoffset, datalen;
u_int32_t wholemark, mark, ifi;
struct nfqnl_msg_packet_hdr *ph;
unsigned char * data;
l7_connection * connection;
ph = nfq_get_msg_packet_hdr(tb);
if(ph){
id = ntohl(ph->packet_id);
l7printf(4, "hw_protocol = 0x%04x hook = %u id = %u ",
ntohs(ph->hw_protocol), ph->hook, id);
}
// Need to get the wholemark so that we can pass the unmasked part back
// Except for the print statement and debugging, there's not really any
// reason to pull out the masked part, because it's always modified without
// looking at it...
wholemark = (nfq_get_nfmark(tb));
if(clobbermark){
mark = UNTOUCHED;
wholemark = wholemark&(~markmask); // zero out our part of the mark
}
else mark = ((wholemark&markmask) >> maskfirstbit);
l7printf(4, "wholemark = %#08x ", wholemark);
l7printf(4, "mark = %d ", mark);
ifi = nfq_get_indev(tb);
if(ifi) l7printf(4, "indev = %d ", ifi);
ifi = nfq_get_outdev(tb);
if(ifi) l7printf(4, "outdev = %d ", ifi);
ret = nfq_get_payload(tb, &data);
if(ret >= 0) l7printf(4, "payload_len = %d\n", ret);
char ip_protocol = data[9];
// Ignore anything that's not TCP or UDP
if(ip_protocol != IPPROTO_TCP && ip_protocol != IPPROTO_UDP)
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
dataoffset = app_data_offset(data);
datalen = ret - dataoffset;
//find the conntrack
string key = l7_connection_tracker->make_key(data, false);
connection = l7_connection_tracker->get_l7_connection(key);
if(connection)
l7printf(3, "Found connection orig:\t%s\n", key.c_str());
if(!connection){
//find the conntrack (backwards)
string key = l7_connection_tracker->make_key(data, true);
connection = l7_connection_tracker->get_l7_connection(key);
if(connection)
l7printf(3, "Found connection reply:\t%s\n", key.c_str());
// It seems to routinely not get the UDP conntrack until the 2nd or 3rd
// packet. Tested with DNS.
if(!connection)
l7printf(2, "Got packet, had no ct:\t%s\n", key.c_str());
}
// mark = the mark we found on the packet
// connection->get_mark() = the mark that we have made internally
if(connection){
connection->increment_num_packets();
if(datalen <= 0){
l7printf(3, "Connection with no new application data ignored.\n");
mark = NO_MATCH_YET; // no application data
}
else{
if(connection->get_mark() != NO_MATCH_YET &&
connection->get_mark() != UNTOUCHED){
// It is classified already. Reapply existing mark.
mark = connection->get_mark();
}
else if(connection->get_num_packets() <= maxpackets){
// Do the heavy lifting.
connection->append_to_buffer((char*)(data+dataoffset),ret-dataoffset);
l7printf(3, "Packet #%d, data is: %s\n", connection->get_num_packets(),
friendly_print((unsigned char *)connection->buffer,
connection->lengthsofar).c_str());
mark = connection->classify();
if(mark != NO_MATCH_YET){ // Got a match, no need to keep data
free(connection->buffer);
connection->buffer = NULL; // marks it not to be free'd again
}
}
else{ // num_packets > maxpackets and hasn't been classified
mark = NO_MATCH;
// if this is the first packet after we've given up, clean up
if(connection->get_num_packets() == maxpackets+1){
print_give_up(key, (unsigned char *)connection->buffer,
connection->lengthsofar);
free(connection->buffer);
connection->buffer = NULL; // marks it not to be free'd again
} // endif should clean up
} // endif whether should run match or what
} // endif there is any new data
} // endif we found the connection
else{
l7printf(3, "Didn't yet find\t%s\n", key.c_str());
mark = NO_MATCH_YET;
}
if(mark == UNTOUCHED) cerr << "NOT REACHED. mark is still UNTOUCHED.\n";
l7printf(4,"Set verdict ACCEPT, mark %#08x\n",(mark<<maskfirstbit)|wholemark);
return nfq_set_verdict_mark(qh, id, NF_ACCEPT,
htonl((mark<<maskfirstbit)|wholemark), 0, NULL);
}
/* Returns offset the into the skb->data that the application data starts */
int l7_queue::app_data_offset(const unsigned char *data)
{
int ip_hl = 4*(data[0] & 0x0f);
char ip_protocol = data[9];
if(ip_protocol == IPPROTO_TCP){
// 12 == offset into TCP header for the header length field.
int tcp_hl = 4*(data[ip_hl + 12]>>4);
return ip_hl + tcp_hl;
}
else if(ip_protocol == IPPROTO_UDP){
return ip_hl + 8; /* UDP header is always 8 bytes */
}
else{
l7printf(0, "Tried to get app data offset for unsupported protocol!\n");
return ip_hl + 8; /* something reasonable */
}
}