]> git.ipfire.org Git - thirdparty/xtables-addons.git/commitdiff
xt_geoip: IPv6 support
authorJan Engelhardt <jengelh@medozas.de>
Wed, 2 Feb 2011 01:01:28 +0000 (02:01 +0100)
committerJan Engelhardt <jengelh@medozas.de>
Wed, 2 Feb 2011 03:47:28 +0000 (04:47 +0100)
doc/changelog.txt
extensions/libxt_geoip.c
extensions/xt_geoip.c
extensions/xt_geoip.h

index 6b28002f2eac964d2ada0b1524fdbbab844c21a6..5d31f9c1f5cf7f1c9abe0b4625eee51d9c3637a9 100644 (file)
@@ -4,6 +4,8 @@ HEAD
 Fixes:
 - Update to ipset 4.5
   * the iptreemap type used wrong gfp flags when deleting entries
+Enhancements:
+- IPv6 support for xt_geoip
 
 
 v1.31 (2010-11-05)
index e6fbdd3ae3c0228c2f4b540ed6297f0d80229170..dcaa434b1eea8c98ad5221ef2a80116a8e6fcfdf 100644 (file)
@@ -2,7 +2,7 @@
  *     "geoip" match extension for iptables
  *     Copyright © Samuel Jean <peejix [at] people netfilter org>, 2004 - 2008
  *     Copyright © Nicolas Bouliane <acidfu [at] people netfilter org>, 2004 - 2008
- *     Jan Engelhardt <jengelh [at] medozas de>, 2008
+ *     Jan Engelhardt <jengelh [at] medozas de>, 2008-2011
  *
  *     This program is free software; you can redistribute it and/or
  *     modify it under the terms of the GNU General Public License; either
@@ -49,20 +49,28 @@ static struct option geoip_opts[] = {
        {NULL},
 };
 
-static struct geoip_subnet4 *
-geoip_get_subnets(const char *code, uint32_t *count)
+static void *
+geoip_get_subnets(const char *code, uint32_t *count, uint8_t nfproto)
 {
-       struct geoip_subnet4 *subnets;
+       void *subnets;
        struct stat sb;
        char buf[256];
        int fd;
 
        /* Use simple integer vector files */
+       if (nfproto == NFPROTO_IPV6) {
 #if __BYTE_ORDER == _BIG_ENDIAN
-       snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/BE/%s.iv4", code);
+               snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/BE/%s.iv6", code);
 #else
-       snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv4", code);
+               snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv6", code);
 #endif
+       } else {
+#if __BYTE_ORDER == _BIG_ENDIAN
+               snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/BE/%s.iv4", code);
+#else
+               snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv4", code);
+#endif
+       }
 
        if ((fd = open(buf, O_RDONLY)) < 0) {
                fprintf(stderr, "Could not open %s: %s\n", buf, strerror(errno));
@@ -70,20 +78,31 @@ geoip_get_subnets(const char *code, uint32_t *count)
        }
 
        fstat(fd, &sb);
-       if (sb.st_size % sizeof(struct geoip_subnet4) != 0)
-               xtables_error(OTHER_PROBLEM, "Database file %s seems to be "
-                          "corrupted", buf);
+       *count = sb.st_size;
+       switch (nfproto) {
+       case NFPROTO_IPV6:
+               if (sb.st_size % sizeof(struct geoip_subnet6) != 0)
+                       xtables_error(OTHER_PROBLEM,
+                               "Database file %s seems to be corrupted", buf);
+               *count /= sizeof(struct geoip_subnet6);
+               break;
+       case NFPROTO_IPV4:
+               if (sb.st_size % sizeof(struct geoip_subnet4) != 0)
+                       xtables_error(OTHER_PROBLEM,
+                               "Database file %s seems to be corrupted", buf);
+               *count /= sizeof(struct geoip_subnet4);
+               break;
+       }
        subnets = malloc(sb.st_size);
        if (subnets == NULL)
                xtables_error(OTHER_PROBLEM, "geoip: insufficient memory");
        read(fd, subnets, sb.st_size);
        close(fd);
-       *count = sb.st_size / sizeof(struct geoip_subnet4);
        return subnets;
 }
  
 static struct geoip_country_user *geoip_load_cc(const char *code,
-    unsigned short cc)
+    unsigned short cc, uint8_t nfproto)
 {
        struct geoip_country_user *ginfo;
        ginfo = malloc(sizeof(struct geoip_country_user));
@@ -91,7 +110,8 @@ static struct geoip_country_user *geoip_load_cc(const char *code,
        if (!ginfo)
                return NULL;
 
-       ginfo->subnets = (unsigned long)geoip_get_subnets(code, &ginfo->count);
+       ginfo->subnets = (unsigned long)geoip_get_subnets(code,
+                        &ginfo->count, nfproto);
        ginfo->cc = cc;
 
        return ginfo;
@@ -134,7 +154,7 @@ check_geoip_cc(char *cc, u_int16_t cc_used[], u_int8_t count)
 }
 
 static unsigned int parse_geoip_cc(const char *ccstr, uint16_t *cc,
-    union geoip_country_group *mem)
+    union geoip_country_group *mem, uint8_t nfproto)
 {
        char *buffer, *cp, *next;
        u_int8_t i, count = 0;
@@ -151,7 +171,8 @@ static unsigned int parse_geoip_cc(const char *ccstr, uint16_t *cc,
                if (next) *next++ = '\0';
 
                if ((cctmp = check_geoip_cc(cp, cc, count)) != 0) {
-                       if ((mem[count++].user = (unsigned long)geoip_load_cc(cp, cctmp)) == 0)
+                       if ((mem[count++].user =
+                           (unsigned long)geoip_load_cc(cp, cctmp, nfproto)) == 0)
                                xtables_error(OTHER_PROBLEM,
                                        "geoip: insufficient memory available");
                        cc[count-1] = cctmp;
@@ -170,11 +191,9 @@ static unsigned int parse_geoip_cc(const char *ccstr, uint16_t *cc,
        return count;
 }
 
-static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
-    const void *entry, struct xt_entry_match **match)
+static int geoip_parse(int c, bool invert, unsigned int *flags,
+    const char *arg, struct xt_geoip_match_info *info, uint8_t nfproto)
 {
-       struct xt_geoip_match_info *info = (void *)(*match)->data;
-
        switch (c) {
        case '1':
                if (*flags & (XT_GEOIP_SRC | XT_GEOIP_DST))
@@ -186,7 +205,8 @@ static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
                if (invert)
                        *flags |= XT_GEOIP_INV;
 
-               info->count = parse_geoip_cc(argv[optind-1], info->cc, info->mem);
+               info->count = parse_geoip_cc(arg, info->cc, info->mem,
+                             nfproto);
                info->flags = *flags;
                return true;
 
@@ -200,7 +220,8 @@ static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
                if (invert)
                        *flags |= XT_GEOIP_INV;
 
-               info->count = parse_geoip_cc(argv[optind-1], info->cc, info->mem);
+               info->count = parse_geoip_cc(arg, info->cc, info->mem,
+                             nfproto);
                info->flags = *flags;
                return true;
        }
@@ -208,6 +229,20 @@ static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
        return false;
 }
 
+static int geoip_parse6(int c, char **argv, int invert, unsigned int *flags,
+    const void *entry, struct xt_entry_match **match)
+{
+       return geoip_parse(c, invert, flags, optarg,
+              (void *)(*match)->data, NFPROTO_IPV6);
+}
+
+static int geoip_parse4(int c, char **argv, int invert, unsigned int *flags,
+    const void *entry, struct xt_entry_match **match)
+{
+       return geoip_parse(c, invert, flags, optarg,
+              (void *)(*match)->data, NFPROTO_IPV4);
+}
+
 static void
 geoip_final_check(unsigned int flags)
 {
@@ -260,22 +295,39 @@ geoip_save(const void *ip, const struct xt_entry_match *match)
        printf(" ");
 }
 
-static struct xtables_match geoip_match = {
-        .family        = NFPROTO_IPV4,
-        .name          = "geoip",
-        .revision      = 1,
-        .version       = XTABLES_VERSION,
-        .size          = XT_ALIGN(sizeof(struct xt_geoip_match_info)),
-        .userspacesize = offsetof(struct xt_geoip_match_info, mem),
-        .help          = geoip_help,
-        .parse         = geoip_parse,
-        .final_check   = geoip_final_check,
-        .print         = geoip_print,
-        .save          = geoip_save,
-        .extra_opts    = geoip_opts,
+static struct xtables_match geoip_match[] = {
+       {
+               .family        = NFPROTO_IPV6,
+               .name          = "geoip",
+               .revision      = 1,
+               .version       = XTABLES_VERSION,
+               .size          = XT_ALIGN(sizeof(struct xt_geoip_match_info)),
+               .userspacesize = offsetof(struct xt_geoip_match_info, mem),
+               .help          = geoip_help,
+               .parse         = geoip_parse6,
+               .final_check   = geoip_final_check,
+               .print         = geoip_print,
+               .save          = geoip_save,
+               .extra_opts    = geoip_opts,
+       },
+       {
+               .family        = NFPROTO_IPV4,
+               .name          = "geoip",
+               .revision      = 1,
+               .version       = XTABLES_VERSION,
+               .size          = XT_ALIGN(sizeof(struct xt_geoip_match_info)),
+               .userspacesize = offsetof(struct xt_geoip_match_info, mem),
+               .help          = geoip_help,
+               .parse         = geoip_parse4,
+               .final_check   = geoip_final_check,
+               .print         = geoip_print,
+               .save          = geoip_save,
+               .extra_opts    = geoip_opts,
+       },
 };
 
 static __attribute__((constructor)) void geoip_mt_ldr(void)
 {
-       xtables_register_match(&geoip_match);
+       xtables_register_matches(geoip_match,
+               sizeof(geoip_match) / sizeof(*geoip_match));
 }
index 2f3caaed1aaa945271b31d597c0cb7e6aaebc186..c84b764154e96f8d8157018191bee990e707ab07 100644 (file)
@@ -9,6 +9,7 @@
  * Samuel Jean & Nicolas Bouliane
  */
 #include <linux/ip.h>
+#include <linux/ipv6.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/module.h>
@@ -27,31 +28,49 @@ MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Nicolas Bouliane");
 MODULE_AUTHOR("Samuel Jean");
 MODULE_DESCRIPTION("xtables module for geoip match");
+MODULE_ALIAS("ip6t_geoip");
 MODULE_ALIAS("ipt_geoip");
 
+enum geoip_proto {
+       GEOIPROTO_IPV6,
+       GEOIPROTO_IPV4,
+       __GEOIPROTO_MAX,
+};
+
 /**
  * @list:      anchor point for geoip_head
- * @subnets:   packed ordered list of ranges
+ * @subnets:   packed ordered list of ranges (either v6 or v4)
  * @count:     number of ranges
  * @cc:                country code
  */
 struct geoip_country_kernel {
        struct list_head list;
-       struct geoip_subnet4 *subnets;
+       void *subnets;
        atomic_t ref;
        unsigned int count;
        unsigned short cc;
 };
 
-static LIST_HEAD(geoip_head);
+static struct list_head geoip_head[__GEOIPROTO_MAX];
 static DEFINE_SPINLOCK(geoip_lock);
 
+static const enum geoip_proto nfp2geo[] = {
+       [NFPROTO_IPV6] = GEOIPROTO_IPV6,
+       [NFPROTO_IPV4] = GEOIPROTO_IPV4,
+};
+static const size_t geoproto_size[] = {
+       [GEOIPROTO_IPV6] = sizeof(struct geoip_subnet6),
+       [GEOIPROTO_IPV4] = sizeof(struct geoip_subnet4),
+};
+
 static struct geoip_country_kernel *
-geoip_add_node(const struct geoip_country_user __user *umem_ptr)
+geoip_add_node(const struct geoip_country_user __user *umem_ptr,
+               enum geoip_proto proto)
 {
        struct geoip_country_user umem;
        struct geoip_country_kernel *p;
-       struct geoip_subnet4 *subnet;
+       size_t size;
+       void *subnet;
        int ret;
 
        if (copy_from_user(&umem, umem_ptr, sizeof(umem)) != 0)
@@ -63,15 +82,14 @@ geoip_add_node(const struct geoip_country_user __user *umem_ptr)
 
        p->count   = umem.count;
        p->cc      = umem.cc;
-
-       subnet = vmalloc(p->count * sizeof(struct geoip_subnet4));
+       size = p->count * geoproto_size[proto];
+       subnet = vmalloc(size);
        if (subnet == NULL) {
                ret = -ENOMEM;
                goto free_p;
        }
        if (copy_from_user(subnet,
-           (const void __user *)(unsigned long)umem.subnets,
-           p->count * sizeof(struct geoip_subnet4)) != 0) {
+           (const void __user *)(unsigned long)umem.subnets, size) != 0) {
                ret = -EFAULT;
                goto free_s;
        }
@@ -81,7 +99,7 @@ geoip_add_node(const struct geoip_country_user __user *umem_ptr)
        INIT_LIST_HEAD(&p->list);
 
        spin_lock(&geoip_lock);
-       list_add_tail_rcu(&p->list, &geoip_head);
+       list_add_tail_rcu(&p->list, &geoip_head[proto]);
        spin_unlock(&geoip_lock);
 
        return p;
@@ -112,12 +130,13 @@ static void geoip_try_remove_node(struct geoip_country_kernel *p)
        kfree(p);
 }
 
-static struct geoip_country_kernel *find_node(unsigned short cc)
+static struct geoip_country_kernel *find_node(unsigned short cc,
+    enum geoip_proto proto)
 {
        struct geoip_country_kernel *p;
        spin_lock(&geoip_lock);
 
-       list_for_each_entry_rcu(p, &geoip_head, list)
+       list_for_each_entry_rcu(p, &geoip_head[proto], list)
                if (p->cc == cc) {
                        atomic_inc(&p->ref);
                        spin_unlock(&geoip_lock);
@@ -128,6 +147,72 @@ static struct geoip_country_kernel *find_node(unsigned short cc)
        return NULL;
 }
 
+static inline int
+ipv6_cmp(const struct in6_addr *p, const struct in6_addr *q)
+{
+       unsigned int i;
+
+       for (i = 0; i < 4; ++i) {
+               if (p->s6_addr32[i] < q->s6_addr32[i])
+                       return -1;
+               else if (p->s6_addr32[i] > q->s6_addr32[i])
+                       return 1;
+       }
+
+       return 0;
+}
+
+static bool geoip_bsearch6(const struct geoip_subnet6 *range,
+    const struct in6_addr *addr, int lo, int hi)
+{
+       int mid;
+
+       if (hi <= lo)
+               return false;
+       mid = (lo + hi) / 2;
+       if (ipv6_cmp(&range[mid].begin, addr) <= 0 &&
+           ipv6_cmp(addr, &range[mid].end) <= 0)
+               return true;
+       if (ipv6_cmp(&range[mid].begin, addr) > 0)
+               return geoip_bsearch6(range, addr, lo, mid);
+       else if (ipv6_cmp(&range[mid].end, addr) < 0)
+               return geoip_bsearch6(range, addr, mid + 1, hi);
+
+       WARN_ON(true);
+       return false;
+}
+
+static bool
+xt_geoip_mt6(const struct sk_buff *skb, struct xt_action_param *par)
+{
+       const struct xt_geoip_match_info *info = par->matchinfo;
+       const struct geoip_country_kernel *node;
+       const struct ipv6hdr *iph = ipv6_hdr(skb);
+       unsigned int i;
+       struct in6_addr ip;
+
+       memcpy(&ip, (info->flags & XT_GEOIP_SRC) ? &iph->saddr : &iph->daddr,
+              sizeof(ip));
+       for (i = 0; i < 4; ++i)
+               ip.s6_addr32[i] = ntohl(ip.s6_addr32[i]);
+
+       rcu_read_lock();
+       for (i = 0; i < info->count; i++) {
+               if ((node = info->mem[i].kernel) == NULL) {
+                       pr_err("'%c%c' is not loaded into memory... skip it!\n",
+                              COUNTRY(info->cc[i]));
+                       continue;
+               }
+               if (geoip_bsearch6(node->subnets, &ip, 0, node->count)) {
+                       rcu_read_unlock();
+                       return !(info->flags & XT_GEOIP_INV);
+               }
+       }
+
+       rcu_read_unlock();
+       return info->flags & XT_GEOIP_INV;
+}
+
 static bool geoip_bsearch4(const struct geoip_subnet4 *range,
     uint32_t addr, int lo, int hi)
 {
@@ -181,9 +266,10 @@ static int xt_geoip_mt_checkentry(const struct xt_mtchk_param *par)
        unsigned int i;
 
        for (i = 0; i < info->count; i++) {
-               node = find_node(info->cc[i]);
+               node = find_node(info->cc[i], nfp2geo[par->family]);
                if (node == NULL) {
-                       node = geoip_add_node((const void __user *)(unsigned long)info->mem[i].user);
+                       node = geoip_add_node((const void __user *)(unsigned long)info->mem[i].user,
+                              nfp2geo[par->family]);
                        if (IS_ERR(node)) {
                                printk(KERN_ERR
                                                "xt_geoip: unable to load '%c%c' into memory: %ld\n",
@@ -228,25 +314,41 @@ static void xt_geoip_mt_destroy(const struct xt_mtdtor_param *par)
                                        "xt_geoip: please report this bug to the maintainers\n");
 }
 
-static struct xt_match xt_geoip_match __read_mostly = {
-       .name       = "geoip",
-       .revision   = 1,
-       .family     = NFPROTO_IPV4,
-       .match      = xt_geoip_mt4,
-       .checkentry = xt_geoip_mt_checkentry,
-       .destroy    = xt_geoip_mt_destroy,
-       .matchsize  = sizeof(struct xt_geoip_match_info),
-       .me         = THIS_MODULE,
+static struct xt_match xt_geoip_match[] __read_mostly = {
+       {
+               .name       = "geoip",
+               .revision   = 1,
+               .family     = NFPROTO_IPV6,
+               .match      = xt_geoip_mt6,
+               .checkentry = xt_geoip_mt_checkentry,
+               .destroy    = xt_geoip_mt_destroy,
+               .matchsize  = sizeof(struct xt_geoip_match_info),
+               .me         = THIS_MODULE,
+       },
+       {
+               .name       = "geoip",
+               .revision   = 1,
+               .family     = NFPROTO_IPV4,
+               .match      = xt_geoip_mt4,
+               .checkentry = xt_geoip_mt_checkentry,
+               .destroy    = xt_geoip_mt_destroy,
+               .matchsize  = sizeof(struct xt_geoip_match_info),
+               .me         = THIS_MODULE,
+       },
 };
 
 static int __init xt_geoip_mt_init(void)
 {
-       return xt_register_match(&xt_geoip_match);
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(geoip_head); ++i)
+               INIT_LIST_HEAD(&geoip_head[i]);
+       return xt_register_matches(xt_geoip_match, ARRAY_SIZE(xt_geoip_match));
 }
 
 static void __exit xt_geoip_mt_fini(void)
 {
-       xt_unregister_match(&xt_geoip_match);
+       xt_unregister_matches(xt_geoip_match, ARRAY_SIZE(xt_geoip_match));
 }
 
 module_init(xt_geoip_mt_init);
index c447e617d29ca6caed4fe88de697ff4527277a49..ee6e6628048fb579ebb980338976cf797ab2c21a 100644 (file)
@@ -27,6 +27,10 @@ struct geoip_subnet4 {
        __u32 end;
 };
 
+struct geoip_subnet6 {
+       struct in6_addr begin, end;
+};
+
 struct geoip_country_user {
        aligned_u64 subnets;
        __u32 count;