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:
- 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)

View 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));
}

View 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);

View 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;