[PATCH] ng_tag - new netgraph node,
please test (L7 filtering possibility)
Vadim Goncharov
vadim_nuclight at mail.ru
Mon Jun 12 12:50:25 UTC 2006
12.06.06 @ 05:34 Eduardo Meyer wrote:
> I read the messages and man page but did not understand. Maybe it is
> my lack of knowledge regarding netgraph? Well, in man page it seems
> that you looked at ipfw source code (.h in fact) to find out the tag
> number. Can you explain this?
Yes, netgraph always was a semi-programmer system, less or more,
especially true with ng_tag, as it tries to be generalized mbuf_tags(9)
manipulating interface, and this is more kernel internals. For simple
using, however, you don't need to bother all that details - just remember
magic number and where to place it, and it is now simple for use with ipfw
tags.
> A practical example, how could I, for example, block Kazaa or
> bittorrent based on L7 with ng_tag? Can you please explain the steps
> on how to do this?
The truth is that, in fact, ng_tag doesn't do any traffic analysis. It
merely provides an easy way to distinguish different packets after
returning to ipfw. Currently the only analyzing node in FreeBSD src tree
is ng_bpf(4), but it merely splits incoming packets in two streams,
matched and not. There are reasons to this, as netgraph needs to be
modular, and each node does a small thing, but does it well. For long time
ng_bpf was used for another purposes in the kernel, and now, as new ipfw
features appeared, ng_tag came up for easy integration.
So, that's merely a framework allowing you to create custom filters, and
if you need to match some kind of traffic, you should sit, understand what
patterns that traffic has and then program ng_bpf(4) with appropriate
filter. In fact, it allows to create it from tcpdump(1) expressions, so
you don't need to be a C programmer, and that's good, isn't it? :)
> I don't run -CURRENT but I need this kind of feature very much, I am
> downloading a 7.0 snapshot just to test this with ipfw tag.
You'll be able to do this with RELENG_6 about two weeks later. I simply
couldn't wait a month for MFC and wrote it earlier :)
> How this addresses the problem on system level L7 filtering? I always
> though that someone would show up with a userland application that
> tags packets and returns the tag to ipfw filtering, but you came up
> with a kernel approach. How better and why it is when compared to evil
> regexp evaluation on kernel or how efficient is this when compared to
> Linux L7 which is know to fail a lot (let a number of packets pass)?
Yes, in general case you do - correct way is to have a userland
application which will do analysis, this easier, simpler and safer
(imagine a security flaw inside kernel matcher?). Like snort. But the
main disadvantage - it is SLOW. And for many kinds of traffic you do not
need to perform complete flow analysis, as that is simple enough to do
per-packet matching, then to say "Huh.. I found such packet, so entire
connection must be of that type". Actually, I've found Linux iptables P2P
matching module named ipp2p at http://www.ipp2p.org/ which was told to
work reasonable well, looked at the code and found that one-packet match
is enough for this work. So, per-packet matching can be implemented in
kernel.
After that I've discovered that FreeBSD already have in-kernel packet
matcher for a long time, since 4.0. Briefly inspecting ipp2p code shown
that most recognized P2P types can be matched by tcpdump and thus are
programmable on ng_bpf(4). For some patterns, still, that's not enough, as
bpf can't search for a substring on a variable, not fixed, offset. Then we
can imagine another netgraph node which will do substring search (like
iptables --string), so with both bpf and string-matching all P2P traffic
can be caught.
Anyway, that work yet to be done. The main benefit of ng_tag at the moment
is that everybody wishing this have no longer principial barriers to do,
like needing skills to write kernel module or even userland matching
daemon.
> Sorry for all those questions, but I am an end user in the average,
> so, I can not understand it myself only reading the code.
>
> Thank you for your work and help. It seems that I will have a 7.0
> snapshot doing this job to me untill the ipfw tag MFC happens, if I
> can understand this approach.
I hope that my explanation was helpful enough to understand :) Also, if
you will be using 7.0, include BPF_JITTER in your kernel config as this
will enable native code-compiling for bpf and ng_bpf - this will speed
things up.
==========================================================================
P.S. Here is quick-and-dirty primer how to convert ipp2p functions to
ng_bpf(4) input expression for tcpdump(1). Go to http://www.ipp2p.org/ and
download source, unpack and open file pt_ipp2p.c and find function for
your P2P type, let it be BitTorrent for our example. So look (I've
formatted that bad Linux code a little to be a more style(9)'ish):
int
search_bittorrent (const unsigned char *payload, const u16 plen)
{
if (plen > 20) {
/* test for match 0x13+"BitTorrent protocol" */
if (payload[0] == 0x13)
if (memcmp(payload+1, "BitTorrent protocol", 19) == 0)
return (IPP2P_BIT * 100);
/* get tracker commandos, all starts with GET /
* then it can follow: scrape| announce
* and then ?hash_info=
*/
if (memcmp(payload,"GET /",5) == 0) {
/* message scrape */
if (memcmp(payload+5, "scrape?info_hash=", 17)==0)
return (IPP2P_BIT * 100 + 1);
/* message announce */
if (memcmp(payload+5, "announce?info_hash=", 19)==0)
return (IPP2P_BIT * 100 + 2);
}
} else {
/*
* bitcomet encryptes the first packet, so we have to detect another
* one later in the flow
*/
/* first try failed, too many missdetections */
//if (size == 5 && get_u32(t,0) == __constant_htonl(1) && t[4] < 3)
// return (IPP2P_BIT * 100 + 3);
/* second try: block request packets */
if ((plen == 17) &&
(get_u32(payload,0) == __constant_htonl(0x0d)) &&
(payload[4] == 0x06) &&
(get_u32(payload,13) == __constant_htonl(0x4000)))
return (IPP2P_BIT * 100 + 3);
}
return 0;
}
So, what do we see? BitTorrent packet can start with one of three fixed
strings (we see memcmp() checks for them). Author of ipp2p employs one
more check, but as we can see from comments, he's not sure.
Let's find out what are the byte sequences for these strings:
$ echo -n "BitTorrent protocol" | hd
00000000 42 69 74 54 6f 72 72 65 6e 74 20 70 72 6f 74 6f |BitTorrent
proto|
00000010 63 6f 6c |col|
00000013
$ echo -n "GET /scrape?info_hash=" | hd
00000000 47 45 54 20 2f 73 63 72 61 70 65 3f 69 6e 66 6f |GET
/scrape?info|
00000010 5f 68 61 73 68 3d |_hash=|
00000016
$ echo -n "GET /announce?info_hash=" | hd
00000000 47 45 54 20 2f 61 6e 6e 6f 75 6e 63 65 3f 69 6e |GET
/announce?in|
00000010 66 6f 5f 68 61 73 68 3d |fo_hash=|
00000018
We can give 1, 2 or 4 bytes to tcpdump for comarison at one time. The
"payload" variable in the source points to beginning of data in TCP
packet. Remember from man ng_tag that tcpdump assumes packets to have
14-byte Ethernet header for it's arrays like "tcp[]", but packets come
from ipfw to ng_bpf without this header, and that affects our offset
calculations. So we must give offsets from very beginning of packets,
which is done through "ether[]" tcpdump's prime, and parse headers
manually. Let's assume (for simplicity and speed), however, that IP and
TCP headers have no any options and thus always have length 20 bytes each,
then ipp2p's "payload[0]" will be tcpdump's "ether[40]". Also, let's
assume that ipfw checked packet len for us so we don't do that in netgraph
too.
Then, we simply take hex bytes in order hd(1) told us, as this is network
byte order also, and write them as tcpdump expressions (remember that
first string ("...protocol") actually have 0x13 prepended to it). So, we
write follow in ng_bpf(4) script:
PATTERN="(ether[40:4]=0x13426974 &&
ether[44:4]=0x546f7272 &&
ether[48:4]=0x656e7420 &&
ether[52:4]=0x70726f74 &&
ether[56:4]=0x6f636f6c
) ||
(ether[40:4]=0x47455420 &&
(ether[44:4]=0x2f736372 &&
ether[48:4]=0x6170653f &&
ether[52:4]=0x696e666f &&
ether[56:4]=0x5f686173 &&
ether[60:2]=0x683d
) ||
(ether[44:4]=0x2f616e6e &&
ether[48:4]=0x6f756e63 &&
ether[52:4]=0x653f696e &&
ether[56:4]=0x666f5f68 &&
ether[60:4]=0x6173683d)
) ||
(ether[2:2]=57 &&
ether[40:4]=0x0000000d &&
ether[44]=0x06 &&
ether[53:4]=0x00004000)"
Note the last OR block in expression - this is translation of that "not
sure" checking request packets. I've explicitly written packet length -
plen=17 + 20 byte IP header len + 20 byte TCP header len, check at offset
2 in IP header, according to RFC 791. Construction "get_u32 ==
__constant_htonl()" means comparing 4-byte values at given offset.
P.P.S. I have not tested that pattern on real packets, as I have no
BitTorrent today, but it should work.
--
WBR, Vadim Goncharov
More information about the freebsd-isp
mailing list