diff --git a/extensions/Kbuild b/extensions/Kbuild index 3c25fa2..a88dff5 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -11,6 +11,7 @@ obj-${build_ECHO} += xt_ECHO.o obj-${build_LOGMARK} += xt_LOGMARK.o obj-${build_TARPIT} += xt_TARPIT.o obj-${build_TEE} += xt_TEE.o +obj-${build_geoip} += xt_geoip.o obj-${build_portscan} += xt_portscan.o -include ${M}/*.Kbuild diff --git a/extensions/libxt_geoip.c b/extensions/libxt_geoip.c new file mode 100644 index 0000000..1783a85 --- /dev/null +++ b/extensions/libxt_geoip.c @@ -0,0 +1,278 @@ +/* Shared library add-on to iptables to add geoip match support. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Copyright (c) 2004, 2005, 2006, 2007, 2008 + * Samuel Jean & Nicolas Bouliane + * + * For comments, bugs or suggestions, please contact + * Samuel Jean + * Nicolas Bouliane + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xt_geoip.h" +#define GEOIP_DB_DIR "/var/geoip" + +static void geoip_help(void) +{ + printf ( + "geoip match options:\n" + "[!] --src-cc, --source-country country[,country...]\n" + " Match packet coming from (one of) the specified country(ies)\n" + "[!] --dst-cc, --destination-country country[,country...]\n" + " Match packet going to (one of) the specified country(ies)\n" + "\n" + "NOTE: The country is inputed by its ISO3166 code.\n" + "\n" + ); +} + +static struct option geoip_opts[] = { + {.name = "dst-cc", .has_arg = true, .val = '2'}, + {.name = "destination-country", .has_arg = true, .val = '2'}, + {.name = "src-cc", .has_arg = true, .val = '1'}, + {.name = "source-country", .has_arg = true, .val = '1'}, + {NULL}, +}; + +static struct geoip_subnet *geoip_get_subnets(const char *code, uint32_t *count) +{ + struct geoip_subnet *subnets; + struct stat sb; + char buf[256]; + int fd; + + /* Use simple integer vector files */ +#if __BYTE_ORDER == _BIG_ENDIAN + snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/BE/%s.iv0", code); +#else + snprintf(buf, sizeof(buf), GEOIP_DB_DIR "/LE/%s.iv0", code); +#endif + + if ((fd = open(buf, O_RDONLY)) < 0) { + fprintf(stderr, "Could not open %s: %s\n", buf, strerror(errno)); + exit_error(OTHER_PROBLEM, "Could not read geoip database"); + } + + fstat(fd, &sb); + if (sb.st_size % sizeof(struct geoip_subnet) != 0) + exit_error(OTHER_PROBLEM, "Database file %s seems to be " + "corrupted", buf); + subnets = malloc(sb.st_size); + if (subnets == NULL) + exit_error(OTHER_PROBLEM, "geoip: insufficient memory"); + read(fd, subnets, sb.st_size); + close(fd); + *count = sb.st_size / sizeof(struct geoip_subnet); + return subnets; +} + +static struct geoip_country_user *geoip_load_cc(const char *code, + unsigned short cc) +{ + struct geoip_country_user *ginfo; + ginfo = malloc(sizeof(struct geoip_country_user)); + + if (!ginfo) + return NULL; + + ginfo->subnets = (unsigned long)geoip_get_subnets(code, &ginfo->count); + ginfo->cc = cc; + + return ginfo; +} + +static u_int16_t +check_geoip_cc(char *cc, u_int16_t cc_used[], u_int8_t count) +{ + u_int8_t i; + u_int16_t cc_int16; + + if (strlen(cc) != 2) /* Country must be 2 chars long according + to the ISO3166 standard */ + exit_error(PARAMETER_PROBLEM, + "geoip: invalid country code '%s'", cc); + + // Verification will fail if chars aren't uppercased. + // Make sure they are.. + for (i = 0; i < 2; i++) + if (isalnum(cc[i]) != 0) + cc[i] = toupper(cc[i]); + else + exit_error(PARAMETER_PROBLEM, + "geoip: invalid country code '%s'", cc); + + /* Convert chars into a single 16 bit integer. + * FIXME: This assumes that a country code is + * exactly 2 chars long. If this is + * going to change someday, this whole + * match will need to be rewritten, anyway. + * - SJ */ + cc_int16 = (cc[0] << 8) | cc[1]; + + // Check for presence of value in cc_used + for (i = 0; i < count; i++) + if (cc_int16 == cc_used[i]) + return 0; // Present, skip it! + + return cc_int16; +} + +static unsigned int parse_geoip_cc(const char *ccstr, uint16_t *cc, + union geoip_country_group *mem) +{ + char *buffer, *cp, *next; + u_int8_t i, count = 0; + u_int16_t cctmp; + + buffer = strdup(ccstr); + if (!buffer) + exit_error(OTHER_PROBLEM, + "geoip: insufficient memory available"); + + for (cp = buffer, i = 0; cp && i < XT_GEOIP_MAX; cp = next, i++) + { + next = strchr(cp, ','); + 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) + exit_error(OTHER_PROBLEM, + "geoip: insufficient memory available"); + cc[count-1] = cctmp; + } + } + + if (cp) + exit_error(PARAMETER_PROBLEM, + "geoip: too many countries specified"); + free(buffer); + + if (count == 0) + exit_error(PARAMETER_PROBLEM, + "geoip: don't know what happened"); + + return count; +} + +static int geoip_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_match **match) +{ + struct xt_geoip_match_info *info = (void *)(*match)->data; + + switch(c) { + case '1': + // Ensure that XT_GEOIP_SRC *OR* XT_GEOIP_DST haven't been used yet. + if (*flags & (XT_GEOIP_SRC | XT_GEOIP_DST)) + exit_error(PARAMETER_PROBLEM, + "geoip: only use --source-country *OR* --destination-country once!"); + + *flags |= XT_GEOIP_SRC; + break; + + case '2': + // Ensure that XT_GEOIP_SRC *OR* XT_GEOIP_DST haven't been used yet. + if (*flags & (XT_GEOIP_SRC | XT_GEOIP_DST)) + exit_error(PARAMETER_PROBLEM, + "geoip: only use --source-country *OR* --destination-country once!"); + + *flags |= XT_GEOIP_DST; + break; + + default: + return 0; + } + + if (invert) + *flags |= XT_GEOIP_INV; + + info->count = parse_geoip_cc(argv[optind-1], info->cc, info->mem); + info->flags = *flags; + return 1; +} + +static void +geoip_final_check(unsigned int flags) +{ + if (!flags) + exit_error(PARAMETER_PROBLEM, + "geoip: missing arguments"); +} + +static void +geoip_print(const void *ip, const struct xt_entry_match *match, int numeric) +{ + const struct xt_geoip_match_info *info = (void*)match->data; + + u_int8_t i; + + if (info->flags & XT_GEOIP_SRC) + printf("Source "); + else + printf("Destination "); + + if (info->count > 1) + printf("countries: "); + else + printf("country: "); + + if (info->flags & XT_GEOIP_INV) + printf("! "); + + for (i = 0; i < info->count; i++) + printf("%s%c%c", i ? "," : "", COUNTRY(info->cc[i])); + printf(" "); +} + +static void +geoip_save(const void *ip, const struct xt_entry_match *match) +{ + const struct xt_geoip_match_info *info = (void *)match->data; + u_int8_t i; + + if (info->flags & XT_GEOIP_INV) + printf("! "); + + if (info->flags & XT_GEOIP_SRC) + printf("--source-country "); + else + printf("--destination-country "); + + for (i = 0; i < info->count; i++) + printf("%s%c%c", i ? "," : "", COUNTRY(info->cc[i])); + printf(" "); +} + +static struct xtables_match geoip_match = { + .family = AF_INET, + .name = "geoip", + .version = XTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct xt_geoip_match_info)), + .userspacesize = XT_ALIGN(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 void _init(void) +{ + xtables_register_match(&geoip_match); +} diff --git a/extensions/libxt_geoip.man b/extensions/libxt_geoip.man new file mode 100644 index 0000000..5f7ca04 --- /dev/null +++ b/extensions/libxt_geoip.man @@ -0,0 +1,16 @@ +Match a packet by its source or destination country. +.TP +[\fB!\fP] \fB--src-cc\fP, \fB--source-country\fP \fIcountry\fP[\fB,\fP\fIcountry\fP\fB...\fP] +Match packet coming from (one of) the specified country(ies) +.TP +[\fB!\fP] \fB--dst-cc\fP, \fB--destination-country\fP \fIcountry\fP[\fB,\fP\fIcountry\fP\fB...\fP] +Match packet going to (one of) the specified country(ies) +.TP +NOTE: +The country is inputed by its ISO3166 code. +.P +The extra files you will need is the binary database files. They are generated +from a country-subnet database with the geoip_csv_iv0.pl tool, available at +http://jengelh.hopto.org/files/geoip/ . The files MUST be moved to /var/geoip/ +as the shared library is statically looking for this pathname (e.g. +/var/geoip/LE/de.iv0). diff --git a/extensions/xt_geoip.c b/extensions/xt_geoip.c new file mode 100644 index 0000000..8b37b17 --- /dev/null +++ b/extensions/xt_geoip.c @@ -0,0 +1,246 @@ +/* iptables kernel module for the geoip match + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Copyright (c) 2004, 2005, 2006, 2007, 2008 + * Samuel Jean & Nicolas Bouliane + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xt_geoip.h" +#include "compat_xtables.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nicolas Bouliane"); +MODULE_AUTHOR("Samuel Jean"); +MODULE_DESCRIPTION("xtables module for geoip match"); +MODULE_ALIAS("ipt_geoip"); + +struct geoip_country_kernel { + struct list_head list; + struct geoip_subnet *subnets; + atomic_t ref; + unsigned int count; + unsigned short cc; +}; + +static LIST_HEAD(geoip_head); +static DEFINE_SPINLOCK(geoip_lock); + +static struct geoip_country_kernel * +geoip_add_node(const struct geoip_country_user __user *umem_ptr) +{ + struct geoip_country_user umem; + struct geoip_country_kernel *p; + struct geoip_subnet *s; + + if (copy_from_user(&umem, umem_ptr, sizeof(umem)) != 0) + return NULL; + + p = kmalloc(sizeof(struct geoip_country_kernel), GFP_KERNEL); + if (p == NULL) + return NULL; + + p->count = umem.count; + p->cc = umem.cc; + + s = vmalloc(p->count * sizeof(struct geoip_subnet)); + if (s == NULL) + goto free_p; + if (copy_from_user(s, (const void __user *)(unsigned long)umem.subnets, + p->count * sizeof(struct geoip_subnet)) != 0) + goto free_s; + + p->subnets = s; + atomic_set(&p->ref, 1); + INIT_LIST_HEAD(&p->list); + + spin_lock(&geoip_lock); + list_add_tail_rcu(&p->list, &geoip_head); + spin_unlock(&geoip_lock); + + return p; + + free_s: + vfree(s); + free_p: + kfree(p); + return NULL; +} + +static void geoip_try_remove_node(struct geoip_country_kernel *p) +{ + spin_lock(&geoip_lock); + if (!atomic_dec_and_test(&p->ref)) { + spin_unlock(&geoip_lock); + return; + } + + /* So now am unlinked or the only one alive, right ? + * What are you waiting ? Free up some memory! + */ + list_del_rcu(&p->list); + spin_unlock(&geoip_lock); + + synchronize_rcu(); + vfree(p->subnets); + kfree(p); +} + +static struct geoip_country_kernel *find_node(unsigned short cc) +{ + struct geoip_country_kernel *p; + spin_lock(&geoip_lock); + + list_for_each_entry_rcu(p, &geoip_head, list) + if (p->cc == cc) { + atomic_inc(&p->ref); + spin_unlock(&geoip_lock); + return p; + } + + spin_unlock(&geoip_lock); + return NULL; +} + +static bool geoip_bsearch(const struct geoip_subnet *range, + uint32_t addr, int lo, int hi) +{ + int mid; + + if (hi < lo) + return false; + mid = (lo + hi) / 2; + if (range[mid].begin <= addr && addr <= range[mid].end) + return true; + if (range[mid].begin > addr) + return geoip_bsearch(range, addr, lo, mid - 1); + else if (range[mid].end < addr) + return geoip_bsearch(range, addr, mid + 1, hi); + + WARN_ON(true); + return false; +} + +static bool xt_geoip_mt(const struct sk_buff *skb, const struct net_device *in, + const struct net_device *out, const struct xt_match *match, + const void *matchinfo, int offset, unsigned int protoff, bool *hotdrop) +{ + const struct xt_geoip_match_info *info = matchinfo; + const struct geoip_country_kernel *node; + const struct iphdr *iph = ip_hdr(skb); + unsigned int i; + uint32_t ip; + + if (info->flags & XT_GEOIP_SRC) + ip = ntohl(iph->saddr); + else + ip = ntohl(iph->daddr); + + rcu_read_lock(); + for (i = 0; i < info->count; i++) { + if ((node = info->mem[i].kernel) == NULL) { + printk(KERN_ERR "xt_geoip: what the hell ?? '%c%c' isn't loaded into memory... skip it!\n", + COUNTRY(info->cc[i])); + + continue; + } + + if (geoip_bsearch(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 xt_geoip_mt_checkentry(const char *table, const void *entry, + const struct xt_match *match, void *matchinfo, unsigned int hook_mask) +{ + struct xt_geoip_match_info *info = matchinfo; + struct geoip_country_kernel *node; + unsigned int i; + + for (i = 0; i < info->count; i++) { + node = find_node(info->cc[i]); + if (node == NULL) + if ((node = geoip_add_node((const void __user *)(unsigned long)info->mem[i].user)) == NULL) { + printk(KERN_ERR + "xt_geoip: unable to load '%c%c' into memory\n", + COUNTRY(info->cc[i])); + return false; + } + + /* Overwrite the now-useless pointer info->mem[i] with + * a pointer to the node's kernelspace structure. + * This avoids searching for a node in the match() and + * destroy() functions. + */ + info->mem[i].kernel = node; + } + + return true; +} + +static void xt_geoip_mt_destroy(const struct xt_match *match, void *matchinfo) +{ + struct xt_geoip_match_info *info = matchinfo; + struct geoip_country_kernel *node; + unsigned int i; + + /* This entry has been removed from the table so + * decrease the refcount of all countries it is + * using. + */ + + for (i = 0; i < info->count; i++) + if ((node = info->mem[i].kernel) != NULL) { + /* Free up some memory if that node isn't used + * anymore. */ + geoip_try_remove_node(node); + } + else + /* Something strange happened. There's no memory allocated for this + * country. Please send this bug to the mailing list. */ + printk(KERN_ERR + "xt_geoip: What happened peejix ? What happened acidfu ?\n" + "xt_geoip: please report this bug to the maintainers\n"); +} + +static struct xt_match xt_geoip_match __read_mostly = { + .family = AF_INET, + .name = "geoip", + .match = xt_geoip_mt, + .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); +} + +static void __exit xt_geoip_mt_fini(void) +{ + xt_unregister_match(&xt_geoip_match); +} + +module_init(xt_geoip_mt_init); +module_exit(xt_geoip_mt_fini); diff --git a/extensions/xt_geoip.h b/extensions/xt_geoip.h new file mode 100644 index 0000000..291c108 --- /dev/null +++ b/extensions/xt_geoip.h @@ -0,0 +1,54 @@ +/* ipt_geoip.h header file for libipt_geoip.c and ipt_geoip.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Copyright (c) 2004, 2005, 2006, 2007, 2008 + * + * Samuel Jean + * Nicolas Bouliane + */ +#ifndef _LINUX_NETFILTER_XT_GEOIP_H +#define _LINUX_NETFILTER_XT_GEOIP_H 1 + +enum { + XT_GEOIP_SRC = 1 << 0, /* Perform check on Source IP */ + XT_GEOIP_DST = 1 << 1, /* Perform check on Destination IP */ + XT_GEOIP_INV = 1 << 2, /* Negate the condition */ + + XT_GEOIP_MAX = 15, /* Maximum of countries */ +}; + +/* Yup, an address range will be passed in with host-order */ +struct geoip_subnet { + __u32 begin; + __u32 end; +}; + +struct geoip_country_user { + aligned_u64 subnets; + __u32 count; + __u16 cc; +}; + +struct geoip_country_kernel; + +union geoip_country_group { + aligned_u64 user; + struct geoip_country_kernel *kernel; +}; + +struct xt_geoip_match_info { + __u8 flags; + __u8 count; + __u16 cc[XT_GEOIP_MAX]; + + /* Used internally by the kernel */ + union geoip_country_group mem[XT_GEOIP_MAX]; +}; + +#define COUNTRY(cc) (cc >> 8), (cc & 0x00FF) + +#endif /* _LINUX_NETFILTER_XT_GEOIP_H */ diff --git a/mconfig b/mconfig index d881610..83e6798 100644 --- a/mconfig +++ b/mconfig @@ -9,4 +9,5 @@ build_ECHO= build_LOGMARK=m build_TARPIT=m build_TEE=m +build_geoip=m build_portscan=m