Merge reworked geoip extension

This commit is contained in:
Jan Engelhardt
2008-03-22 05:16:51 +01:00
6 changed files with 596 additions and 0 deletions

View File

@@ -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

278
extensions/libxt_geoip.c Normal file
View File

@@ -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 <peejix@people.netfilter.org>
* Nicolas Bouliane <peejix@people.netfilter.org>
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <xtables.h>
#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);
}

View File

@@ -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).

246
extensions/xt_geoip.c Normal file
View File

@@ -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 <linux/ip.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/rcupdate.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <linux/vmalloc.h>
#include <linux/netfilter/x_tables.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
#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);

54
extensions/xt_geoip.h Normal file
View File

@@ -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 */

View File

@@ -9,4 +9,5 @@ build_ECHO=
build_LOGMARK=m
build_TARPIT=m
build_TEE=m
build_geoip=m
build_portscan=m