From: Samuel Jean <sj-netfilter@cookinglinux.org>
To: netfilter-devel@lists.netfilter.org
Subject: New match ipt_geoip
Date: Wed, 03 Nov 2004 22:08:07 -0500 [thread overview]
Message-ID: <41899D17.7060800@cookinglinux.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 2147 bytes --]
Hi people,
We've been working on a way to match a packet based on its country.
We know it's not a 100% reliable way to filter, but it all
depends on the database one use.
This could be usefull for packets classification though.
Let's explain the concept.
First, you need a database - as specified below - which contains
field of the form :
"begin_subnet","end_subnet","bin_start","bin_end","ISO Code","Country"
exemple:
"2.6.190.56","2.6.190.63","33996344","33996351","GB","United Kingdom"
This is the MaxMind's GeoIP CSV db format. But we obviously don't need
all those values, so we've written a tool called csv2bin that converts
this database type to a smaller binary format.
csv2bin is available at www.cookinglinux.org/geoip/
People can get free MaxMind's GeoIP database at www.maxmind.com
This tools create 2 files, geoipdb.bin (the database)
and geoipdb.idx (the index file). Unfortunatelly, we need to move both
files in /var/geoip/ by default - unless someone rewrite the static path
in the shared library.
That's about all for this fuzzy extra requierement.
The match options look like :
[!] --src-cc, --source-country <country code>
[!] --dst-cc, --destination-country <country code>
NOTE: The country is inputed by its ISO3166 code.
You can match up to 15 countries in a rule.
-A INPUT -m geoip --source-country ca,us,jp,de,a1,a2
The library loads subnets of specified countries into user-memory
and passes pointers to the module which copies it into kernelspace.
If a country is specified more than once, a reference count is increased
for that country. When there's no more ref count, that country is freed
from memory.
What would be great is a caching system. Going to implement it when
we'll have time.
It all works for both linux-2.4 and 2.6.
Well, that's enough for theory that you can all presume by reading the
source code, so here it is.
(I'll put source for linux-2.4 but 2.6 is also provided in the pom-ng
package)
We'd like to thank Martin Josefsson for the great help he gave us.
Comments are greatly welcome.
Nicolas Bouliane,
Samuel Jean
at cookinglinux.org
[-- Attachment #2: ipt_geoip.h --]
[-- Type: text/x-chdr, Size: 865 bytes --]
#ifndef _IPT_GEOIP_H
#define _IPT_GEOIP_H
#define IPT_GEOIP_SRC 0x01 /* Perform check on Source IP */
#define IPT_GEOIP_DST 0x02 /* Perform check on Destination IP */
#define IPT_GEOIP_INV 0x04 /* Negate the condition */
#define IPT_GEOIP_MAX 15 /* Maximum of countries */
struct geoip_subnet {
u_int32_t begin;
u_int32_t end;
};
struct geoip_info {
struct geoip_subnet *subnets;
u_int32_t count;
u_int32_t ref;
u_int16_t cc;
struct geoip_info *next;
struct geoip_info *prev;
};
struct ipt_geoip_info {
u_int8_t flags;
u_int8_t count;
u_int16_t cc[IPT_GEOIP_MAX];
/* Used internally by the kernel */
struct geoip_info *mem[IPT_GEOIP_MAX];
u_int8_t *refcount;
/* not implemented yet:
void *fini;
*/
};
#define COUNTRY(cc) (cc >> 8), (cc & 0x00FF)
#endif
[-- Attachment #3: ipt_geoip.c --]
[-- Type: text/x-csrc, Size: 7541 bytes --]
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/netfilter_ipv4/ipt_geoip.h>
#include <linux/netfilter_ipv4/ip_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Samuel Jean, Nicolas Bouliane");
MODULE_DESCRIPTION("iptables/netfilter's geoip match");
struct geoip_info *head = NULL;
static spinlock_t geoip_lock = SPIN_LOCK_UNLOCKED;
static struct geoip_info *add_node(struct geoip_info *memcpy)
{
struct geoip_info *p =
(struct geoip_info *)kmalloc(sizeof(struct geoip_info), GFP_KERNEL);
struct geoip_subnet *s =
(struct geoip_subnet *)kmalloc(p->count * sizeof(struct geoip_subnet), GFP_KERNEL);
if ((p == NULL) || (copy_from_user(p, memcpy, sizeof(struct geoip_info)) != 0))
return NULL;
if ((s == NULL) || (copy_from_user(s, p->subnets, p->count * sizeof(struct geoip_subnet)) != 0))
return NULL;
spin_lock_bh(&geoip_lock);
p->subnets = s;
p->ref = 1;
p->next = head;
p->prev = NULL;
if (p->next) p->next->prev = p;
head = p;
spin_unlock_bh(&geoip_lock);
return p;
}
static void remove_node(struct geoip_info *p)
{
spin_lock_bh(&geoip_lock);
if (p->next) { /* Am I following a node ? */
p->next->prev = p->prev;
if (p->prev) p->prev->next = p->next; /* Is there a node behind me ? */
else head = p->next; /* No? Then I was the head */
}
else
if (p->prev) /* Is there a node behind me ? */
p->prev->next = NULL;
else
head = NULL; /* No, we're alone */
/* So now am unlinked or the only one alive, right ?
* What are you waiting ? Free up some memory!
*/
kfree(p->subnets);
kfree(p);
spin_unlock_bh(&geoip_lock);
return;
}
static struct geoip_info *find_node(u_int16_t cc)
{
struct geoip_info *p = head;
spin_lock_bh(&geoip_lock);
while (p) {
if (p->cc == cc) {
spin_unlock_bh(&geoip_lock);
return p;
}
p = p->next;
}
spin_unlock_bh(&geoip_lock);
return NULL;
}
static int match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
const void *hdr,
u_int16_t datalen,
int *hotdrop)
{
const struct ipt_geoip_info *info = matchinfo;
const struct geoip_info *node; /* This keeps the code sexy */
const struct iphdr *iph = skb->nh.iph;
u_int32_t ip, j;
u_int8_t i;
if (info->flags & IPT_GEOIP_SRC)
ip = ntohl(iph->saddr);
else
ip = ntohl(iph->daddr);
spin_lock_bh(&geoip_lock);
for (i = 0; i < info->count; i++) {
if ((node = info->mem[i]) == NULL) {
printk(KERN_ERR "ipt_geoip: what the hell ?? '%c%c' isn't loaded into memory... skip it!\n",
COUNTRY(info->cc[i]));
continue;
}
for (j = 0; j < node->count; j++)
if ((ip > node->subnets[j].begin) && (ip < node->subnets[j].end)) {
spin_unlock_bh(&geoip_lock);
return (info->flags & IPT_GEOIP_INV) ? 0 : 1;
}
}
spin_unlock_bh(&geoip_lock);
return (info->flags & IPT_GEOIP_INV) ? 1 : 0;
}
static int geoip_checkentry(const char *tablename,
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
struct ipt_geoip_info *info = matchinfo;
struct geoip_info *node;
u_int8_t i;
/* FIXME: Call a function to free userspace allocated memory.
* As Martin J. said; this match might eat lot of memory
* if commited with iptables-restore --noflush
void (*gfree)(struct geoip_info *oldmem);
gfree = info->fini;
*/
if (matchsize != IPT_ALIGN(sizeof(struct ipt_geoip_info))) {
printk(KERN_ERR "ipt_geoip: matchsize differ, you may have forgotten to recompile me\n");
return 0;
}
/* If info->refcount isn't NULL, then
* it means that checkentry() already
* initialized this entry. Increase a
* refcount to prevent destroy() of
* this entry. */
if (info->refcount != NULL) {
atomic_inc((atomic_t *)info->refcount);
return 1;
}
for (i = 0; i < info->count; i++) {
if ((node = find_node(info->cc[i])) != NULL)
atomic_inc((atomic_t *)&node->ref); //increase the reference
else
if ((node = add_node(info->mem[i])) == NULL) {
printk(KERN_ERR
"ipt_geoip: unable to load '%c%c' into memory\n",
COUNTRY(info->cc[i]));
return 0;
}
/* Free userspace allocated memory for that country.
* FIXME: It's a bit odd to call this function everytime
* we process a country. Would be nice to call
* it once after all countries've been processed.
* - SJ
* *not implemented for now*
gfree(info->mem[i]);
*/
/* 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] = node;
}
/* We allocate some memory and give info->refcount a pointer
* to this memory. This prevents checkentry() from increasing a refcount
* different from the one used by destroy().
* For explanation, see http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html
*/
info->refcount = kmalloc(sizeof(u_int8_t), GFP_KERNEL);
if (info->refcount == NULL) {
printk(KERN_ERR "ipt_geoip: failed to allocate `refcount' memory\n");
return 0;
}
*(info->refcount) = 1;
return 1;
}
static void geoip_destroy(void *matchinfo, unsigned int matchsize)
{
struct ipt_geoip_info *info = matchinfo;
struct geoip_info *node; /* this keeps the code sexy */
u_int8_t i;
/* Decrease the previously increased refcount in checkentry()
* If it's equal to 1, we know this entry is just moving
* but not removed. We simply return to avoid useless destroy()
* processing.
*/
atomic_dec((atomic_t *)info->refcount);
if (*info->refcount)
return;
/* 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]) != NULL) {
atomic_dec((atomic_t *)&node->ref);
/* Free up some memory if that node isn't used
* anymore. */
if (node->ref < 1)
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
"ipt_geoip: What happened peejix ? What happened acidmen ?\n"
"ipt_geoip: please report this bug to the maintainers\n");
return;
}
static struct ipt_match geoip_match
= { { NULL, NULL }, "geoip", &match, &geoip_checkentry, &geoip_destroy, THIS_MODULE };
static int __init init(void)
{
return ipt_register_match(&geoip_match);
}
static void __exit fini(void)
{
ipt_unregister_match(&geoip_match);
return;
}
module_init(init);
module_exit(fini);
[-- Attachment #4: libipt_geoip.c --]
[-- Type: text/x-csrc, Size: 11400 bytes --]
/* Shared library add-on to iptables to add geoip match support.
*
* For comments, bugs or suggestions, please contact
* Samuel Jean <sjean at cookinglinux.org>
* Nicolas Bouliane <nib at cookinglinux.org>
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
#include <stddef.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <iptables.h>
#include <linux/netfilter_ipv4/ipt_geoip.h>
// We need it to verify inputed country code
// This shouldn't go in ipt_geoip.h because only this library needs it.
// Also, those country codes *MUST* stand in alphabetic order due to the
// algorithm used to seek through this list.
#define COUNTRYCOUNT 243 /* Always re-adjust this value when
adding/removing a country */
#define COUNTRYCODESZ 2 // This value shouldn't be changed.
static char *cc_list[COUNTRYCOUNT] = {
"A1","A2", // Anonymous Proxies and Satellite Providers
"AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ",
"BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV",
"BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR",
"CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER",
"ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI",
"GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT",
"HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH",
"KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT",
"LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR",
"MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO",
"NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS",
"PT","PW","PY","QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI",
"SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH",
"TJ","TK","TM","TN","TO","TP","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY",
"UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","YU","ZA","ZM","ZR",
"ZW" };
static void help(void)
{
printf (
"GeoIP v%s options:\n"
" [!] --src-cc, --source-country country[,country,country,...]\n"
" Match packet coming from (one of)\n"
" the specified country(ies)\n"
"\n"
" [!] --dst-cc, --destination-country country[,country,country,...]\n"
" Match packet going to (one of)\n"
" the specified country(ies)\n"
"\n"
" NOTE: The country is inputed by its ISO3166 code.\n"
"\n"
"\n", IPTABLES_VERSION
);
}
static struct option opts[] = {
{ "dst-cc", 1, 0, '2' }, /* Alias for --destination-country */
{ "destination-country", 1, 0, '2' },
{ "src-cc", 1, 0, '1' }, /* Alias for --source-country */
{ "source-country", 1, 0, '1' },
{ 0 }
};
static void
init(struct ipt_entry_match *m, unsigned int *nfcache)
{
}
/* NOT IMPLEMENTED YET
static void geoip_free(struct geoip_info *oldmem)
{
}
*/
static u_int8_t
binary_search(const char *key, u_int8_t low, u_int8_t hi)
{
u_int8_t mid = (hi-low)/2 + low;
if (low >= hi)
return strncmp(key, cc_list[mid], 2);
if (!strncmp(key, cc_list[mid], 2))
return 0;
if (strncmp(key, cc_list[mid], 2) > 0)
return binary_search(key, mid+1, hi);
else
return binary_search(key, low, mid);
}
struct geoip_index {
u_int16_t cc;
u_int32_t offset;
} __attribute__ ((packed));
struct geoip_subnet *
get_country_subnets(u_int16_t cc, u_int32_t *count)
{
FILE *ixfd, *dbfd;
struct geoip_subnet *subnets;
struct geoip_index *index;
struct stat buf;
size_t idxsz;
u_int16_t i;
u_int16_t db_cc = 0;
u_int16_t db_nsubnets = 0;
if ((ixfd = fopen("/var/geoip/geoipdb.idx", "r")) == NULL) {
perror("/var/geoip/geoipdb.idx");
exit_error(OTHER_PROBLEM,
"geoip match: cannot open geoip's database index file");
}
stat("/var/geoip/geoipdb.idx", &buf);
idxsz = buf.st_size/sizeof(struct geoip_index);
index = (struct geoip_index *)malloc(buf.st_size);
fread(index, buf.st_size, 1, ixfd);
for (i = 0; i < idxsz; i++)
if (cc == index[i].cc)
break;
if (cc != index[i].cc)
exit_error(OTHER_PROBLEM,
"geoip match: sorry, '%c%c' isn't in the database\n", COUNTRY(cc));
fclose(ixfd);
if ((dbfd = fopen("/var/geoip/geoipdb.bin", "r")) == NULL) {
perror("/var/geoip/geoipdb.bin");
exit_error(OTHER_PROBLEM,
"geoip match: cannot open geoip's database file");
}
fseek(dbfd, index[i].offset, SEEK_SET);
fread(&db_cc, sizeof(u_int16_t), 1, dbfd);
if (db_cc != cc)
exit_error(OTHER_PROBLEM,
"geoip match: this shouldn't happened, the database might be corrupted, or there's a bug.\n"
"you should contact maintainers");
fread(&db_nsubnets, sizeof(u_int16_t), 1, dbfd);
subnets = (struct geoip_subnet*)malloc(db_nsubnets * sizeof(struct geoip_subnet));
if (!subnets)
exit_error(OTHER_PROBLEM,
"geoip match: insufficient memory available");
fread(subnets, db_nsubnets * sizeof(struct geoip_subnet), 1, dbfd);
fclose(dbfd);
free(index);
*count = db_nsubnets;
return subnets;
}
static struct geoip_info *
load_geoip_cc(u_int16_t cc)
{
static struct geoip_info *ginfo;
ginfo = malloc(sizeof(struct geoip_info));
if (!ginfo)
return NULL;
ginfo->subnets = get_country_subnets(cc, &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) != COUNTRYCODESZ) /* Country must be 2 chars long according
to the ISO3166 standard */
exit_error(PARAMETER_PROBLEM,
"geoip match: invalid country code '%s'", cc);
// Verification will fail if chars aren't uppercased.
// Make sure they are..
for (i = 0; i < COUNTRYCODESZ; i++)
cc[i] = toupper(cc[i]);
// Verify for a valid value against the country code list.
if (binary_search(cc, 0, COUNTRYCOUNT-1) != 0)
exit_error(PARAMETER_PROBLEM,
"geoip match: 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;
}
/* Based on libipt_multiport.c parsing code. */
static u_int8_t
parse_geoip_cc(const char *ccstr, u_int16_t *cc, struct geoip_info **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 match: insufficient memory available");
for (cp = buffer, i = 0; cp && i < IPT_GEOIP_MAX; cp = next, i++)
{
next = strchr(cp, ',');
if (next) *next++ = '\0';
if ((cctmp = check_geoip_cc(cp, cc, count)) != 0) {
if ((mem[count++] = load_geoip_cc(cctmp)) == NULL)
exit_error(OTHER_PROBLEM,
"geoip match: insufficient memory available");
cc[count-1] = cctmp;
}
}
if (cp) exit_error(PARAMETER_PROBLEM,
"geoip match: too many countries specified");
free(buffer);
if (count == 0) exit_error(PARAMETER_PROBLEM,
"geoip match: don't know what happened");
return count;
}
static int
parse(int c, char **argv, int invert, unsigned int *flags,
const struct ipt_entry *entry,
unsigned int *nfcache,
struct ipt_entry_match **match)
{
struct ipt_geoip_info *info
= (struct ipt_geoip_info *)(*match)->data;
switch(c) {
case '1':
// Ensure that IPT_GEOIP_SRC *OR* IPT_GEOIP_DST haven't been used yet.
if (*flags & (IPT_GEOIP_SRC | IPT_GEOIP_DST))
exit_error(PARAMETER_PROBLEM,
"geoip match: only use --source-country *OR* --destination-country once!");
*flags |= IPT_GEOIP_SRC;
*nfcache |= NFC_IP_SRC;
break;
case '2':
// Ensure that IPT_GEOIP_SRC *OR* IPT_GEOIP_DST haven't been used yet.
if (*flags & (IPT_GEOIP_SRC | IPT_GEOIP_DST))
exit_error(PARAMETER_PROBLEM,
"geoip match: only use --source-country *OR* --destination-country once!");
*flags |= IPT_GEOIP_DST;
*nfcache |= NFC_IP_DST;
break;
default:
return 0;
}
if (invert)
*flags |= IPT_GEOIP_INV;
info->count = parse_geoip_cc(argv[optind-1], info->cc, info->mem);
info->flags = *flags;
info->refcount = NULL;
//info->fini = &geoip_free;
return 1;
}
static void
final_check(unsigned int flags)
{
if (!flags)
exit_error(PARAMETER_PROBLEM,
"geoip match: missing arguments");
}
static void
print(const struct ipt_ip *ip,
const struct ipt_entry_match *match,
int numeric)
{
const struct ipt_geoip_info *info
= (const struct ipt_geoip_info *)match->data;
u_int8_t i;
if (info->flags & IPT_GEOIP_SRC)
printf("Source ");
else printf("Destination ");
if (info->count > 1)
printf("countries: ");
else printf("country: ");
if (info->flags & IPT_GEOIP_INV)
printf("! ");
for (i = 0; i < info->count; i++)
printf("%s%c%c", i ? "," : "", COUNTRY(info->cc[i]));
}
static void
save(const struct ipt_ip *ip,
const struct ipt_entry_match *match)
{
const struct ipt_geoip_info *info
= (const struct ipt_geoip_info *)match->data;
u_int8_t i;
if (info->flags & IPT_GEOIP_INV)
printf("! ");
if (info->flags & IPT_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 iptables_match geoip = {
.name = "geoip",
.version = IPTABLES_VERSION,
.size = IPT_ALIGN(sizeof(struct ipt_geoip_info)),
.userspacesize = offsetof(struct ipt_geoip_info, mem),
.help = &help,
.init = &init,
.parse = &parse,
.final_check = &final_check,
.print = &print,
.save = &save,
.extra_opts = opts
};
void _init(void)
{
register_match(&geoip);
}
[-- Attachment #5: geoip-pomng-20041103-1.tar.gz --]
[-- Type: application/x-gzip, Size: 8090 bytes --]
next reply other threads:[~2004-11-04 3:08 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2004-11-04 3:08 Samuel Jean [this message]
2004-11-05 0:57 ` New match ipt_geoip Samuel Jean
2004-11-05 10:42 ` Harald Welte
2004-11-05 17:50 ` Samuel Jean
2004-11-05 23:27 ` Jozsef Kadlecsik
2004-11-06 9:35 ` Harald Welte
2004-11-08 1:04 ` New match ipt_geoip (GPLed) Samuel Jean
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=41899D17.7060800@cookinglinux.org \
--to=sj-netfilter@cookinglinux.org \
--cc=netfilter-devel@lists.netfilter.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.