xt_geoip: IPv6 support

This commit is contained in:
Jan Engelhardt
2011-02-02 02:01:28 +01:00
parent eb9634aee6
commit 07cd29d9ce
4 changed files with 219 additions and 59 deletions

View File

@@ -4,6 +4,8 @@ HEAD
Fixes: Fixes:
- Update to ipset 4.5 - Update to ipset 4.5
* the iptreemap type used wrong gfp flags when deleting entries * the iptreemap type used wrong gfp flags when deleting entries
Enhancements:
- IPv6 support for xt_geoip
v1.31 (2010-11-05) v1.31 (2010-11-05)

View File

@@ -2,7 +2,7 @@
* "geoip" match extension for iptables * "geoip" match extension for iptables
* Copyright © Samuel Jean <peejix [at] people netfilter org>, 2004 - 2008 * Copyright © Samuel Jean <peejix [at] people netfilter org>, 2004 - 2008
* Copyright © Nicolas Bouliane <acidfu [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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License; either * modify it under the terms of the GNU General Public License; either
@@ -49,20 +49,28 @@ static struct option geoip_opts[] = {
{NULL}, {NULL},
}; };
static struct geoip_subnet4 * static void *
geoip_get_subnets(const char *code, uint32_t *count) geoip_get_subnets(const char *code, uint32_t *count, uint8_t nfproto)
{ {
struct geoip_subnet4 *subnets; void *subnets;
struct stat sb; struct stat sb;
char buf[256]; char buf[256];
int fd; int fd;
/* Use simple integer vector files */ /* Use simple integer vector files */
if (nfproto == NFPROTO_IPV6) {
#if __BYTE_ORDER == _BIG_ENDIAN
snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/BE/%s.iv6", code);
#else
snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv6", code);
#endif
} else {
#if __BYTE_ORDER == _BIG_ENDIAN #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.iv4", code);
#else #else
snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv4", code); snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv4", code);
#endif #endif
}
if ((fd = open(buf, O_RDONLY)) < 0) { if ((fd = open(buf, O_RDONLY)) < 0) {
fprintf(stderr, "Could not open %s: %s\n", buf, strerror(errno)); 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); fstat(fd, &sb);
*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) if (sb.st_size % sizeof(struct geoip_subnet4) != 0)
xtables_error(OTHER_PROBLEM, "Database file %s seems to be " xtables_error(OTHER_PROBLEM,
"corrupted", buf); "Database file %s seems to be corrupted", buf);
*count /= sizeof(struct geoip_subnet4);
break;
}
subnets = malloc(sb.st_size); subnets = malloc(sb.st_size);
if (subnets == NULL) if (subnets == NULL)
xtables_error(OTHER_PROBLEM, "geoip: insufficient memory"); xtables_error(OTHER_PROBLEM, "geoip: insufficient memory");
read(fd, subnets, sb.st_size); read(fd, subnets, sb.st_size);
close(fd); close(fd);
*count = sb.st_size / sizeof(struct geoip_subnet4);
return subnets; return subnets;
} }
static struct geoip_country_user *geoip_load_cc(const char *code, 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; struct geoip_country_user *ginfo;
ginfo = malloc(sizeof(struct geoip_country_user)); ginfo = malloc(sizeof(struct geoip_country_user));
@@ -91,7 +110,8 @@ static struct geoip_country_user *geoip_load_cc(const char *code,
if (!ginfo) if (!ginfo)
return NULL; 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; ginfo->cc = cc;
return ginfo; 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, 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; char *buffer, *cp, *next;
u_int8_t i, count = 0; 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 (next) *next++ = '\0';
if ((cctmp = check_geoip_cc(cp, cc, count)) != 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, xtables_error(OTHER_PROBLEM,
"geoip: insufficient memory available"); "geoip: insufficient memory available");
cc[count-1] = cctmp; cc[count-1] = cctmp;
@@ -170,11 +191,9 @@ static unsigned int parse_geoip_cc(const char *ccstr, uint16_t *cc,
return count; return count;
} }
static int geoip_parse(int c, char **argv, int invert, unsigned int *flags, static int geoip_parse(int c, bool invert, unsigned int *flags,
const void *entry, struct xt_entry_match **match) const char *arg, struct xt_geoip_match_info *info, uint8_t nfproto)
{ {
struct xt_geoip_match_info *info = (void *)(*match)->data;
switch (c) { switch (c) {
case '1': case '1':
if (*flags & (XT_GEOIP_SRC | XT_GEOIP_DST)) 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) if (invert)
*flags |= XT_GEOIP_INV; *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; info->flags = *flags;
return true; return true;
@@ -200,7 +220,8 @@ static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
if (invert) if (invert)
*flags |= XT_GEOIP_INV; *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; info->flags = *flags;
return true; return true;
} }
@@ -208,6 +229,20 @@ static int geoip_parse(int c, char **argv, int invert, unsigned int *flags,
return false; 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 static void
geoip_final_check(unsigned int flags) geoip_final_check(unsigned int flags)
{ {
@@ -260,7 +295,22 @@ geoip_save(const void *ip, const struct xt_entry_match *match)
printf(" "); printf(" ");
} }
static struct xtables_match geoip_match = { 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, .family = NFPROTO_IPV4,
.name = "geoip", .name = "geoip",
.revision = 1, .revision = 1,
@@ -268,14 +318,16 @@ static struct xtables_match geoip_match = {
.size = XT_ALIGN(sizeof(struct xt_geoip_match_info)), .size = XT_ALIGN(sizeof(struct xt_geoip_match_info)),
.userspacesize = offsetof(struct xt_geoip_match_info, mem), .userspacesize = offsetof(struct xt_geoip_match_info, mem),
.help = geoip_help, .help = geoip_help,
.parse = geoip_parse, .parse = geoip_parse4,
.final_check = geoip_final_check, .final_check = geoip_final_check,
.print = geoip_print, .print = geoip_print,
.save = geoip_save, .save = geoip_save,
.extra_opts = geoip_opts, .extra_opts = geoip_opts,
},
}; };
static __attribute__((constructor)) void geoip_mt_ldr(void) static __attribute__((constructor)) void geoip_mt_ldr(void)
{ {
xtables_register_match(&geoip_match); xtables_register_matches(geoip_match,
sizeof(geoip_match) / sizeof(*geoip_match));
} }

View File

@@ -9,6 +9,7 @@
* Samuel Jean & Nicolas Bouliane * Samuel Jean & Nicolas Bouliane
*/ */
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/module.h> #include <linux/module.h>
@@ -27,31 +28,49 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nicolas Bouliane"); MODULE_AUTHOR("Nicolas Bouliane");
MODULE_AUTHOR("Samuel Jean"); MODULE_AUTHOR("Samuel Jean");
MODULE_DESCRIPTION("xtables module for geoip match"); MODULE_DESCRIPTION("xtables module for geoip match");
MODULE_ALIAS("ip6t_geoip");
MODULE_ALIAS("ipt_geoip"); MODULE_ALIAS("ipt_geoip");
enum geoip_proto {
GEOIPROTO_IPV6,
GEOIPROTO_IPV4,
__GEOIPROTO_MAX,
};
/** /**
* @list: anchor point for geoip_head * @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 * @count: number of ranges
* @cc: country code * @cc: country code
*/ */
struct geoip_country_kernel { struct geoip_country_kernel {
struct list_head list; struct list_head list;
struct geoip_subnet4 *subnets; void *subnets;
atomic_t ref; atomic_t ref;
unsigned int count; unsigned int count;
unsigned short cc; unsigned short cc;
}; };
static LIST_HEAD(geoip_head); static struct list_head geoip_head[__GEOIPROTO_MAX];
static DEFINE_SPINLOCK(geoip_lock); 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 * 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_user umem;
struct geoip_country_kernel *p; struct geoip_country_kernel *p;
struct geoip_subnet4 *subnet; size_t size;
void *subnet;
int ret; int ret;
if (copy_from_user(&umem, umem_ptr, sizeof(umem)) != 0) 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->count = umem.count;
p->cc = umem.cc; p->cc = umem.cc;
size = p->count * geoproto_size[proto];
subnet = vmalloc(p->count * sizeof(struct geoip_subnet4)); subnet = vmalloc(size);
if (subnet == NULL) { if (subnet == NULL) {
ret = -ENOMEM; ret = -ENOMEM;
goto free_p; goto free_p;
} }
if (copy_from_user(subnet, if (copy_from_user(subnet,
(const void __user *)(unsigned long)umem.subnets, (const void __user *)(unsigned long)umem.subnets, size) != 0) {
p->count * sizeof(struct geoip_subnet4)) != 0) {
ret = -EFAULT; ret = -EFAULT;
goto free_s; goto free_s;
} }
@@ -81,7 +99,7 @@ geoip_add_node(const struct geoip_country_user __user *umem_ptr)
INIT_LIST_HEAD(&p->list); INIT_LIST_HEAD(&p->list);
spin_lock(&geoip_lock); 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); spin_unlock(&geoip_lock);
return p; return p;
@@ -112,12 +130,13 @@ static void geoip_try_remove_node(struct geoip_country_kernel *p)
kfree(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; struct geoip_country_kernel *p;
spin_lock(&geoip_lock); 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) { if (p->cc == cc) {
atomic_inc(&p->ref); atomic_inc(&p->ref);
spin_unlock(&geoip_lock); spin_unlock(&geoip_lock);
@@ -128,6 +147,72 @@ static struct geoip_country_kernel *find_node(unsigned short cc)
return NULL; 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, static bool geoip_bsearch4(const struct geoip_subnet4 *range,
uint32_t addr, int lo, int hi) 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; unsigned int i;
for (i = 0; i < info->count; 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) { 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)) { if (IS_ERR(node)) {
printk(KERN_ERR printk(KERN_ERR
"xt_geoip: unable to load '%c%c' into memory: %ld\n", "xt_geoip: unable to load '%c%c' into memory: %ld\n",
@@ -228,7 +314,18 @@ static void xt_geoip_mt_destroy(const struct xt_mtdtor_param *par)
"xt_geoip: please report this bug to the maintainers\n"); "xt_geoip: please report this bug to the maintainers\n");
} }
static struct xt_match xt_geoip_match __read_mostly = { 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", .name = "geoip",
.revision = 1, .revision = 1,
.family = NFPROTO_IPV4, .family = NFPROTO_IPV4,
@@ -237,16 +334,21 @@ static struct xt_match xt_geoip_match __read_mostly = {
.destroy = xt_geoip_mt_destroy, .destroy = xt_geoip_mt_destroy,
.matchsize = sizeof(struct xt_geoip_match_info), .matchsize = sizeof(struct xt_geoip_match_info),
.me = THIS_MODULE, .me = THIS_MODULE,
},
}; };
static int __init xt_geoip_mt_init(void) 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) 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); module_init(xt_geoip_mt_init);

View File

@@ -27,6 +27,10 @@ struct geoip_subnet4 {
__u32 end; __u32 end;
}; };
struct geoip_subnet6 {
struct in6_addr begin, end;
};
struct geoip_country_user { struct geoip_country_user {
aligned_u64 subnets; aligned_u64 subnets;
__u32 count; __u32 count;