File: l7-queue.cpp

package info (click to toggle)
l7-filter-userspace 0.12-beta1-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 664 kB
  • sloc: sh: 3,635; cpp: 1,474; makefile: 20
file content (354 lines) | stat: -rw-r--r-- 10,937 bytes parent folder | download | duplicates (2)
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
/*
  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;
  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((const unsigned char*)data);
  datalen = ret - dataoffset;

  //find the conntrack 
  string key = get_conntrack_key((const unsigned char*)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 = get_conntrack_key((const unsigned char*)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 a string that uniquely defines the connection
string l7_queue::get_conntrack_key(const unsigned char *data, bool reverse) 
{
  char * buf = (char *)malloc(256);
  int ip_hl = 4*(data[0] & 0x0f);
  char ip_protocol = data[9];

  if(ip_protocol == IPPROTO_TCP){
    if(reverse){
      snprintf(buf, 255, 
              "tcp      6 src=%d.%d.%d.%d dst=%d.%d.%d.%d sport=%d dport=%d",
	      data[12], data[13], data[14], data[15],
	      data[16], data[17], data[18], data[19],
	      data[ip_hl]*256+data[ip_hl+1], data[ip_hl+2]*256+data[ip_hl+3]);
    }
    else{
      snprintf(buf, 255, 
              "tcp      6 src=%d.%d.%d.%d dst=%d.%d.%d.%d sport=%d dport=%d",
	      data[16], data[17], data[18], data[19],
	      data[12], data[13], data[14], data[15],
	      data[ip_hl+2]*256+data[ip_hl+3], data[ip_hl]*256+data[ip_hl+1]);
    }
  }
  else if(ip_protocol == IPPROTO_UDP){
    if(reverse){
      snprintf(buf, 255, 
              "udp      17 src=%d.%d.%d.%d dst=%d.%d.%d.%d sport=%d dport=%d",
	      data[12], data[13], data[14], data[15],
	      data[16], data[17], data[18], data[19],
	      data[ip_hl]*256+data[ip_hl+1], data[ip_hl+2]*256+data[ip_hl+3]);
    }
    else{
      snprintf(buf, 255, 
              "udp      17 src=%d.%d.%d.%d dst=%d.%d.%d.%d sport=%d dport=%d",
	      data[16], data[17], data[18], data[19],
	      data[12], data[13], data[14], data[15],
	      data[ip_hl+2]*256+data[ip_hl+3], data[ip_hl]*256+data[ip_hl+1]);
    }
  }
  else{
    l7printf(0, "Tried to get conntrack key for unsupported protocol!\n");
    buf[0] = '\0';
  }
  string answer = buf;
  free(buf);

  l7printf(3, "Made key from packet:\t%s\n", answer.c_str());

  return answer;
}

/* 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 */
  }
}