Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

I'm trying to filter on some parts of the payload, for an IPv6 packet with extension headers (for instance Destination Options).

ip6tables works fine with conditions like --proto udp or --dport 109, even when the packet has extension headers. Netfilter clearly knows how to jump over Destination Options to find the UDP header.

Now, I would like to use the u32 module to match a byte in the payload (say "I want the third byte of the payload to be 42). If the packet has no extension headers something like --u32 "48&0x0000ff00=0x2800"̀ (48 = 40 bytes for the IPv6 header + 8 for the UDP header) works fine, If the packet has a Destination Options, it no longer matches. I would like to write a rule that will work whether the packet has Destination Options or not.

I do not find a way to tell Netfilter to parse until the UDP header (something that it is able to do, otherwise --dport 109 would not work) then to leave u32 parse the rest.

I'm looking for a simple way, otherwise, as BatchyX mentions, I could write a kernel module doing what I want.

share|improve this question
1  
Just use NFQUEUE and do filtering in userspace. –  BatchyX Oct 7 '12 at 14:06
1  
Filtering in userspace is very slow especially if you comapre it to u32 matching that is by far fastest way of matching with netfilter. –  Hrvoje Špoljar Oct 7 '12 at 14:07
1  
@HrvojeŠpoljar: slow is better than nothing. If you want speed, what about writing a kernel module so you can access the skb like you want. because as it stands now, u32 doesn't seem to expose the skb's various offsets. –  BatchyX Oct 7 '12 at 14:14
    
gimme some time it's really not that straight forward I'm writing u32 expression to position where guy needs it. It's not fixed of as he explained but it can be dynamically matched based on data one can read from packet –  Hrvoje Špoljar Oct 7 '12 at 14:18
1  
@HrvojeŠpoljar: Forget it. You may have more than one extension headers, and there is no loop in u32. I already wrote IPv6 parsers in userspace, and it's not done as trivially as you think. –  BatchyX Oct 7 '12 at 14:35

2 Answers 2

Answer is focusing on resolving problem in case of IPv4 packet and does not address problem of (multiple) extension headers that need to be accounted for before proper offset is calculated to get to real data part.

Following rule is comprised of 2 parts

  • check if udp 6&0xFF=17
  • check if 3rd data byte is set to 42 (hex 0x2A) 0>>22&0x3C@7&0xFF=0x2A

iptables -I INPUT -m u32 --u32 "6&0xFF=17&&0>>22&0x3C@7&0xFF=0x2A" -m comment --comment "Match udp packet with 3rd data byte set to 42" -j LOG

Checking 3rd byte is tricky since you need to dynamically position in UDP header. UDP Header starts after IP header;

check this header model to get better idea what is where.

As you can see 20 bytes is min length of IP header, but then if there are options of any data it can be more than 20 bytes so you need to dynamically position your self to start of UDP header.

Possition reading from first 4 bytes ( 32 bits / u32)

quote from:

To get the header length, we need the first byte: "0>>24", but we need to only grab the lower nibble and we need to multiply that number by 4 to get the actual number of bytes in the header. To do the multiply, we'll right shift 22 instead of 24. With this shift, we'll need to use a mask of 0x3C instead of the 0x0F we would have used. The expression so far is: "0>>22&0x3C".

Now that we have offset where UDP header starts ( 0>>22&0x3C ) we can just possition our selves to read bytes 7,8,9,10 ( where data bytes are 8,9,10) and cut off with mask 0xFF last of 4 (3rd data byte). "7&0xFF"

share|improve this answer
    
-1 because completely unrelated to my question. With IPv6, the problem are the extension headers and you completely ignore them. –  bortzmeyer Oct 7 '12 at 16:23

I ran into the same problem and patched the u32-module. I have two patches for the 2.6.30 Kernel, one for the kernel and one for the user land, They probably won't handle fragmented payloads any better than the original u32 matcher, but it worked for my problem.


Kernel Patch:

diff -ruNd linux-2.6.30.4patch/net/netfilter/xt_u32.c linux-2.6.30/net/netfilter/xt_u32.c
--- linux-2.6.30.4patch/net/netfilter/xt_u32.c  2014-03-19 13:24:06.000000000 +0100
+++ linux-2.6.30/net/netfilter/xt_u32.c 2014-10-02 13:12:33.244444192 +0200
@@ -13,9 +13,13 @@
 #include <linux/types.h>
 #include <linux/netfilter/x_tables.h>
 #include <linux/netfilter/xt_u32.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+

 static bool u32_match_it(const struct xt_u32 *data,
-            const struct sk_buff *skb)
+            const struct sk_buff *skb,
+            unsigned int PayloadOffset)
 {
    const struct xt_u32_test *ct;
    unsigned int testind;
@@ -34,7 +38,7 @@
    for (testind = 0; testind < data->ntests; ++testind) {
        ct  = &data->tests[testind];
        at  = 0;
-       pos = ct->location[0].number;
+       pos = PayloadOffset + ct->location[0].number;

        if (skb->len < 4 || pos > skb->len - 4)
            return false;
@@ -92,27 +96,91 @@
    const struct xt_u32 *data = par->matchinfo;
    bool ret;

-   ret = u32_match_it(data, skb);
+   ret = u32_match_it(data, skb, 0);
    return ret ^ data->invert;
 }

-static struct xt_match xt_u32_mt_reg __read_mostly = {
-   .name       = "u32",
-   .revision   = 0,
-   .family     = NFPROTO_UNSPEC,
-   .match      = u32_mt,
-   .matchsize  = sizeof(struct xt_u32),
-   .me         = THIS_MODULE,
+static bool u32_tcp_mt(const struct sk_buff *skb, const struct xt_match_param *par)
+{
+   const struct xt_u32 *data = par->matchinfo;
+   const struct tcphdr *th;
+   struct tcphdr _tcph;
+   bool ret;
+
+   th = skb_header_pointer(skb, par->thoff, sizeof(_tcph), &_tcph);
+   if (th == NULL)
+       return false;
+
+   if (th->doff*4 < sizeof(*th))
+       return false;
+
+   /* printk("TCP payload match @%d\n", par->thoff + 4*th->doff); */
+   ret = u32_match_it(data, skb, par->thoff + 4*th->doff);
+   return ret ^ data->invert;
+}
+
+static bool u32_udp_mt(const struct sk_buff *skb, const struct xt_match_param *par)
+{
+   const struct xt_u32 *data = par->matchinfo;
+   bool ret;
+
+   /* printk("UDP payload match @%d\n", par->thoff + sizeof(struct udphdr) ); */
+   ret = u32_match_it(data, skb, par->thoff + sizeof(struct udphdr) );
+   return ret ^ data->invert;
+}
+
+static struct xt_match xt_u32_mt_reg[] __read_mostly = {
+   {
+       .name           = "u32",
+       .revision       = 0,
+       .family         = NFPROTO_UNSPEC,
+       .match          = u32_mt,
+       .matchsize      = sizeof(struct xt_u32),
+       .me             = THIS_MODULE,
+   },
+        {
+                .name           = "payload",
+                .family         = NFPROTO_IPV4,
+                .match          = u32_tcp_mt,
+                .matchsize      = sizeof(struct xt_u32),
+                .proto          = IPPROTO_TCP,
+                .me             = THIS_MODULE,
+        },
+        {
+                .name           = "payload",
+                .family         = NFPROTO_IPV6,
+                .match          = u32_tcp_mt,
+                .matchsize      = sizeof(struct xt_u32),
+                .proto          = IPPROTO_TCP,
+                .me             = THIS_MODULE,
+        },
+        {
+                .name           = "payload",
+                .family         = NFPROTO_IPV4,
+                .match          = u32_udp_mt,
+                .matchsize      = sizeof(struct xt_u32),
+                .proto          = IPPROTO_UDP,
+                .me             = THIS_MODULE,
+        },
+        {
+                .name           = "payload",
+                .family         = NFPROTO_IPV6,
+                .match          = u32_udp_mt,
+                .matchsize      = sizeof(struct xt_u32),
+                .proto          = IPPROTO_UDP,
+                .me             = THIS_MODULE,
+        },
+
 };

 static int __init u32_mt_init(void)
 {
-   return xt_register_match(&xt_u32_mt_reg);
+   return xt_register_matches(xt_u32_mt_reg, ARRAY_SIZE(xt_u32_mt_reg));
 }

 static void __exit u32_mt_exit(void)
 {
-   xt_unregister_match(&xt_u32_mt_reg);
+   xt_unregister_matches(xt_u32_mt_reg, ARRAY_SIZE(xt_u32_mt_reg));
 }

 module_init(u32_mt_init);
@@ -122,3 +190,5 @@
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("ipt_u32");
 MODULE_ALIAS("ip6t_u32");
+MODULE_ALIAS("ipt_payload");
+MODULE_ALIAS("ip6t_payload");

User land patch:

diff -ruNd iptables-1.4.12.1.4patch/extensions/GNUmakefile.in iptables-1.4.12.1/extensions/GNUmakefile.in
--- iptables-1.4.12.1.4patch/extensions/GNUmakefile.in  2014-10-02 14:43:19.000000000 +0200
+++ iptables-1.4.12.1/extensions/GNUmakefile.in 2014-10-02 14:29:54.000000000 +0200
@@ -73,6 +73,7 @@
 install: ${targets_install}
    @mkdir -p "${DESTDIR}${xtlibdir}";
    if test -n "${targets_install}"; then install -pm0755 $^ "${DESTDIR}${xtlibdir}/"; fi;
+   test -f "${DESTDIR}${xtlibdir}/libxt_u32.so" && ln -s "${DESTDIR}${xtlibdir}/libxt_u32.so" "${DESTDIR}${xtlibdir}/libxt_payload.so"

 clean:
    rm -f *.o *.oo *.so *.a {matches,targets}[46].man initext.c initext4.c initext6.c;
diff -ruNd iptables-1.4.12.1.4patch/extensions/libxt_u32.c iptables-1.4.12.1/extensions/libxt_u32.c
--- iptables-1.4.12.1.4patch/extensions/libxt_u32.c 2014-10-02 14:43:19.000000000 +0200
+++ iptables-1.4.12.1/extensions/libxt_u32.c    2014-10-02 13:57:24.000000000 +0200
@@ -31,7 +31,7 @@
 static void u32_help(void)
 {
    printf(
-       "u32 match options:\n"
+       "u32/payload match options:\n"
        "[!] --u32 tests\n"
        "\t\t""tests := location \"=\" value | tests \"&&\" location \"=\" value\n"
        "\t\t""value := range | value \",\" range\n"
@@ -249,7 +249,7 @@
                       int numeric)
 {
    const struct xt_u32 *data = (const void *)match->data;
-   printf(" u32");
+   printf(" %s", match->u.user.name);
    if (data->invert)
        printf(" !");
    u32_dump(data);
@@ -277,7 +277,21 @@
    .x6_options    = u32_opts,
 };

+static struct xtables_match payload_match = {
+   .name          = "payload",
+   .family        = NFPROTO_UNSPEC,
+   .version       = XTABLES_VERSION,
+   .size          = XT_ALIGN(sizeof(struct xt_u32)),
+   .userspacesize = XT_ALIGN(sizeof(struct xt_u32)),
+   .help          = u32_help,
+   .print         = u32_print,
+   .save          = u32_save,
+   .x6_parse      = u32_parse,
+   .x6_options    = u32_opts,
+};
+
 void _init(void)
 {
    xtables_register_match(&u32_match);
+   xtables_register_match(&payload_match);
 }
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.