Date: Wed, 14 Jun 2006 18:31:17 +0400 From: "Alexander V. Chernikov" <admin@su29.net> To: "Vadim Goncharov" <vadim_nuclight@mail.ru> Cc: freebsd-net@freebsd.org, freebsd-current@freebsd.org Subject: Re[2]: [PATCH] ng_tag - new netgraph node, please test (L7 filtering possibility) Message-ID: <783346175.20060614183117@su29.net> In-Reply-To: <opta09vodb17d6mn@nuclight.avtf.net> References: <optax2g7jq4fjv08@nuclight.avtf.net> <70e8236f0606110836j38f7ca33wa3058eaecf386fb5@mail.gmail.com> <optazz26kn17d6mn@nuclight.avtf.net> <d3ea75b30606111534q337aa27aj87baa1f20550ac1c@mail.gmail.com> <opta09vodb17d6mn@nuclight.avtf.net>
next in thread | previous in thread | raw e-mail | index | archive | help
Hello Vadim. you wrote 12 =C9=C0=CE=D1 2006 =C7., 16:48:50: Vadim Goncharov> 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? Vadim Goncharov> Yes, netgraph always was a semi-programmer system, less or= more, =20 Vadim Goncharov> especially true with ng_tag, as it tries to be Vadim Goncharov> generalized mbuf_tags(9) =20 Vadim Goncharov> manipulating interface, and this is more kernel internals.= For simple Vadim Goncharov> using, however, you don't need to bother all that details = - just remember Vadim Goncharov> magic number and where to place it, and it is now Vadim Goncharov> simple for use with ipfw =20 Vadim Goncharov> 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? Unfortunately i have never analyzed p2p protocols, but for ICQ solution is very simple. Of course, this is not what you asked for, but it is a practical example using ng_tag node At the beginning of icq session server always sends us 10 bytes length packet of data: 2A 01 XX XX 00 04 00 00 00 01 ^ ^ ^ ^ ^ ^ ^ ^ SEQ Length ^ Channel ICQ flap ID so we can easily match and block this packet with iplen =3D 50 in ipfw and by 8 bytes exact match in ng_bpf The following line is for ng_bpf(4) script from manpage PATTERN=3D"ether[40:2] =3D 0x2A01 and ether[44:4] =3D 0x00040000 and ether[48:2] =3D 0x0001" Vadim Goncharov> The truth is that, in fact, ng_tag doesn't do any traffic = analysis. It Vadim Goncharov> merely provides an easy way to distinguish different packe= ts after =20 Vadim Goncharov> returning to ipfw. Currently the only analyzing node in Fr= eeBSD src tree Vadim Goncharov> is ng_bpf(4), but it merely splits incoming packets in two= streams, =20 Vadim Goncharov> matched and not. There are reasons to this, as netgraph ne= eds to be =20 Vadim Goncharov> modular, and each node does a small thing, but does it wel= l. For long time Vadim Goncharov> ng_bpf was used for another purposes in the kernel, and no= w, as new ipfw Vadim Goncharov> features appeared, ng_tag came up for easy integration. Vadim Goncharov> So, that's merely a framework allowing you to create custo= m filters, and Vadim Goncharov> if you need to match some kind of traffic, you Vadim Goncharov> should sit, understand what =20 Vadim Goncharov> patterns that traffic has and then program ng_bpf(4) with = appropriate Vadim Goncharov> filter. In fact, it allows to create it from Vadim Goncharov> tcpdump(1) expressions, so =20 Vadim Goncharov> 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. Vadim Goncharov> You'll be able to do this with RELENG_6 about two weeks la= ter. I simply Vadim Goncharov> 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)? Vadim Goncharov> Yes, in general case you do - correct way is to have a use= rland =20 Vadim Goncharov> application which will do analysis, this easier, simpler a= nd safer =20 Vadim Goncharov> (imagine a security flaw inside kernel matcher?). Like sno= rt. But the Vadim Goncharov> main disadvantage - it is SLOW. And for many kinds of traf= fic you do not Vadim Goncharov> need to perform complete flow analysis, as that is simple = enough to do Vadim Goncharov> per-packet matching, then to say "Huh.. I found such packe= t, so entire Vadim Goncharov> connection must be of that type". Actually, I've Vadim Goncharov> found Linux iptables P2P =20 Vadim Goncharov> matching module named ipp2p at http://www.ipp2p.org/ which= was told to Vadim Goncharov> work reasonable well, looked at the code and found that on= e-packet match Vadim Goncharov> is enough for this work. So, per-packet matching can be im= plemented in Vadim Goncharov> kernel. Vadim Goncharov> After that I've discovered that FreeBSD already have in-ke= rnel packet Vadim Goncharov> matcher for a long time, since 4.0. Briefly Vadim Goncharov> inspecting ipp2p code shown =20 Vadim Goncharov> that most recognized P2P types can be matched by tcpdump a= nd thus are Vadim Goncharov> programmable on ng_bpf(4). For some patterns, still, that'= s not enough, as Vadim Goncharov> bpf can't search for a substring on a variable, not fixed,= offset. Then we Vadim Goncharov> can imagine another netgraph node which will do substring = search (like Vadim Goncharov> iptables --string), so with both bpf and Vadim Goncharov> string-matching all P2P traffic =20 Vadim Goncharov> can be caught. Vadim Goncharov> Anyway, that work yet to be done. The main benefit of ng_t= ag at the moment Vadim Goncharov> is that everybody wishing this have no longer Vadim Goncharov> principial barriers to do, =20 Vadim Goncharov> like needing skills to write kernel module or even userlan= d matching =20 Vadim Goncharov> 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. Vadim Goncharov> I hope that my explanation was helpful enough to understan= d :) Also, if Vadim Goncharov> you will be using 7.0, include BPF_JITTER in your kernel c= onfig as this Vadim Goncharov> will enable native code-compiling for bpf and ng_bpf - thi= s will speed Vadim Goncharov> things up. Vadim Goncharov> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D Vadim Goncharov> P.S. Here is quick-and-dirty primer how to convert ipp2p f= unctions to Vadim Goncharov> ng_bpf(4) input expression for tcpdump(1). Go to Vadim Goncharov> http://www.ipp2p.org/ and =20 Vadim Goncharov> download source, unpack and open file pt_ipp2p.c and find = function for Vadim Goncharov> your P2P type, let it be BitTorrent for our example. So lo= ok (I've =20 Vadim Goncharov> formatted that bad Linux code a little to be a more style(= 9)'ish): Vadim Goncharov> int Vadim Goncharov> search_bittorrent (const unsigned char *payload, const u16= plen) Vadim Goncharov> { Vadim Goncharov> if (plen > 20) { Vadim Goncharov> /* test for match 0x13+"BitTorrent protocol" */ Vadim Goncharov> if (payload[0] =3D=3D 0x13) Vadim Goncharov> if (memcmp(payload+1, "BitTorrent protocol= ", 19) =3D=3D 0) Vadim Goncharov> return (IPP2P_BIT * 100); Vadim Goncharov> /* get tracker commandos, all starts with GET / Vadim Goncharov> * then it can follow: scrape| announce Vadim Goncharov> * and then ?hash_info=3D Vadim Goncharov> */ Vadim Goncharov> if (memcmp(payload,"GET /",5) =3D=3D 0) { Vadim Goncharov> /* message scrape */ Vadim Goncharov> if (memcmp(payload+5, "scrape?info_hash=3D= ", 17)=3D=3D0) Vadim Goncharov> return (IPP2P_BIT * 100 + 1); Vadim Goncharov> /* message announce */ Vadim Goncharov> if (memcmp(payload+5, "announce?info_hash= =3D", 19)=3D=3D0) Vadim Goncharov> return (IPP2P_BIT * 100 + 2); Vadim Goncharov> } Vadim Goncharov> } else { Vadim Goncharov> /* Vadim Goncharov> * bitcomet encryptes the first packet, so we have= to detect another Vadim Goncharov> * one later in the flow Vadim Goncharov> */ Vadim Goncharov> /* first try failed, too many missdetections */ Vadim Goncharov> //if (size =3D=3D 5 && get_u32(t,0) =3D=3D Vadim Goncharov> __constant_htonl(1) && t[4] < 3) Vadim Goncharov> // return (IPP2P_BIT * 100 + 3); Vadim Goncharov> =20 Vadim Goncharov> /* second try: block request packets */ Vadim Goncharov> if ((plen =3D=3D 17) && Vadim Goncharov> (get_u32(payload,0) =3D=3D __constant_htonl(0x= 0d)) && Vadim Goncharov> (payload[4] =3D=3D 0x06) && Vadim Goncharov> (get_u32(payload,13) =3D=3D __constant_htonl(0= x4000))) Vadim Goncharov> return (IPP2P_BIT * 100 + 3); Vadim Goncharov> } Vadim Goncharov> return 0; Vadim Goncharov> } Vadim Goncharov> So, what do we see? BitTorrent packet can start with one o= f three fixed Vadim Goncharov> strings (we see memcmp() checks for them). Author of ipp2p= employs one Vadim Goncharov> more check, but as we can see from comments, he's not sure. Vadim Goncharov> Let's find out what are the byte sequences for these strin= gs: Vadim Goncharov> $ echo -n "BitTorrent protocol" | hd Vadim Goncharov> 00000000 42 69 74 54 6f 72 72 65 6e 74 20 70 72 6f 74 6f= |BitTorrent Vadim Goncharov> proto| Vadim Goncharov> 00000010 63 6f 6c = |col| Vadim Goncharov> 00000013 Vadim Goncharov> $ echo -n "GET /scrape?info_hash=3D" | hd Vadim Goncharov> 00000000 47 45 54 20 2f 73 63 72 61 70 65 3f 69 6e 66 6f= |GET =20 Vadim Goncharov> /scrape?info| Vadim Goncharov> 00000010 5f 68 61 73 68 3d = |_hash=3D| Vadim Goncharov> 00000016 Vadim Goncharov> $ echo -n "GET /announce?info_hash=3D" | hd Vadim Goncharov> 00000000 47 45 54 20 2f 61 6e 6e 6f 75 6e 63 65 3f 69 6e= |GET =20 Vadim Goncharov> /announce?in| Vadim Goncharov> 00000010 66 6f 5f 68 61 73 68 3d = |fo_hash=3D| Vadim Goncharov> 00000018 Vadim Goncharov> We can give 1, 2 or 4 bytes to tcpdump for comarison at on= e time. The Vadim Goncharov> "payload" variable in the source points to beginning of da= ta in TCP =20 Vadim Goncharov> packet. Remember from man ng_tag that tcpdump assumes pack= ets to have Vadim Goncharov> 14-byte Ethernet header for it's arrays like Vadim Goncharov> "tcp[]", but packets come =20 Vadim Goncharov> from ipfw to ng_bpf without this header, and that affects= our offset Vadim Goncharov> calculations. So we must give offsets from very beginning = of packets, Vadim Goncharov> which is done through "ether[]" tcpdump's prime, and parse= headers =20 Vadim Goncharov> manually. Let's assume (for simplicity and speed), however= , that IP and Vadim Goncharov> TCP headers have no any options and thus always have lengt= h 20 bytes each, Vadim Goncharov> then ipp2p's "payload[0]" will be tcpdump's "ether[40]". A= lso, let's =20 Vadim Goncharov> assume that ipfw checked packet len for us so we Vadim Goncharov> don't do that in netgraph =20 Vadim Goncharov> too. Vadim Goncharov> Then, we simply take hex bytes in order hd(1) told us, as = this is network Vadim Goncharov> byte order also, and write them as tcpdump Vadim Goncharov> expressions (remember that =20 Vadim Goncharov> first string ("...protocol") actually have 0x13 Vadim Goncharov> prepended to it). So, we =20 Vadim Goncharov> write follow in ng_bpf(4) script: Vadim Goncharov> PATTERN=3D"(ether[40:4]=3D0x13426974 && Vadim Goncharov> ether[44:4]=3D0x546f7272 && Vadim Goncharov> ether[48:4]=3D0x656e7420 && Vadim Goncharov> ether[52:4]=3D0x70726f74 && Vadim Goncharov> ether[56:4]=3D0x6f636f6c Vadim Goncharov> ) || Vadim Goncharov> (ether[40:4]=3D0x47455420 && Vadim Goncharov> (ether[44:4]=3D0x2f736372 && Vadim Goncharov> ether[48:4]=3D0x6170653f && Vadim Goncharov> ether[52:4]=3D0x696e666f && Vadim Goncharov> ether[56:4]=3D0x5f686173 && Vadim Goncharov> ether[60:2]=3D0x683d Vadim Goncharov> ) || Vadim Goncharov> (ether[44:4]=3D0x2f616e6e && Vadim Goncharov> ether[48:4]=3D0x6f756e63 && Vadim Goncharov> ether[52:4]=3D0x653f696e && Vadim Goncharov> ether[56:4]=3D0x666f5f68 && Vadim Goncharov> ether[60:4]=3D0x6173683d) Vadim Goncharov> ) || Vadim Goncharov> (ether[2:2]=3D57 && Vadim Goncharov> ether[40:4]=3D0x0000000d && Vadim Goncharov> ether[44]=3D0x06 && Vadim Goncharov> ether[53:4]=3D0x00004000)" Vadim Goncharov> Note the last OR block in expression - this is Vadim Goncharov> translation of that "not =20 Vadim Goncharov> sure" checking request packets. I've explicitly written pa= cket length - Vadim Goncharov> plen=3D17 + 20 byte IP header len + 20 byte TCP header len= , check at offset Vadim Goncharov> 2 in IP header, according to RFC 791. Construction "get_u3= 2 =3D=3D =20 Vadim Goncharov> __constant_htonl()" means comparing 4-byte values at given= offset. Vadim Goncharov> P.P.S. I have not tested that pattern on real packets, as = I have no =20 Vadim Goncharov> BitTorrent today, but it should work. --=20 Cheers, Alexander V. Chernikov mailto:admin@su29.net
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?783346175.20060614183117>