1/* iptables kernel module for the geoip match 2 * 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * Copyright (c) 2004, 2005, 2006, 2007, 2008 9 * Samuel Jean & Nicolas Bouliane 10 */ 11#include <linux/module.h> 12#include <linux/kernel.h> 13#include <linux/version.h> 14#include <linux/skbuff.h> 15#include <linux/ip.h> 16#include <linux/netdevice.h> 17#include <asm/uaccess.h> 18#include <asm/atomic.h> 19 20#include <linux/netfilter/x_tables.h> 21#include <linux/netfilter/xt_geoip.h> 22 23MODULE_LICENSE("GPL"); 24MODULE_AUTHOR("Nicolas Bouliane"); 25MODULE_AUTHOR("Samuel Jean"); 26MODULE_DESCRIPTION("xtables module for geoip match"); 27MODULE_ALIAS("ipt_geoip"); 28 29struct geoip_info *head = NULL; 30static DEFINE_SPINLOCK(geoip_lock); 31 32static struct geoip_info *add_node(struct geoip_info *memcpy) 33{ 34 struct geoip_info *p = 35 (struct geoip_info *)kmalloc(sizeof(struct geoip_info), GFP_KERNEL); 36 37 struct geoip_subnet *s; 38 39 if ((p == NULL) || (copy_from_user(p, memcpy, sizeof(struct geoip_info)) != 0)) 40 return NULL; 41 42 s = (struct geoip_subnet *)kmalloc(p->count * sizeof(struct geoip_subnet), GFP_KERNEL); 43 if ((s == NULL) || (copy_from_user(s, p->subnets, p->count * sizeof(struct geoip_subnet)) != 0)) 44 return NULL; 45 46 spin_lock_bh(&geoip_lock); 47 48 p->subnets = s; 49 p->ref = 1; 50 p->next = head; 51 p->prev = NULL; 52 if (p->next) p->next->prev = p; 53 head = p; 54 55 spin_unlock_bh(&geoip_lock); 56 return p; 57} 58 59static void remove_node(struct geoip_info *p) 60 { 61 spin_lock_bh(&geoip_lock); 62 63 if (p->next) { /* Am I following a node ? */ 64 p->next->prev = p->prev; 65 if (p->prev) p->prev->next = p->next; /* Is there a node behind me ? */ 66 else head = p->next; /* No? Then I was the head */ 67 } 68 69 else 70 if (p->prev) /* Is there a node behind me ? */ 71 p->prev->next = NULL; 72 else 73 head = NULL; /* No, we're alone */ 74 75 /* So now am unlinked or the only one alive, right ? 76 * What are you waiting ? Free up some memory! 77 */ 78 79 kfree(p->subnets); 80 kfree(p); 81 82 spin_unlock_bh(&geoip_lock); 83 return; 84} 85 86static struct geoip_info *find_node(u_int16_t cc) 87{ 88 struct geoip_info *p = head; 89 spin_lock_bh(&geoip_lock); 90 91 while (p) { 92 if (p->cc == cc) { 93 spin_unlock_bh(&geoip_lock); 94 return p; 95 } 96 p = p->next; 97 } 98 spin_unlock_bh(&geoip_lock); 99 return NULL; 100} 101 102static bool xt_geoip_mt(const struct sk_buff *skb, struct xt_action_param *par) 103{ 104 const struct xt_geoip_match_info *info = par->matchinfo; 105 const struct geoip_info *node; /* This keeps the code sexy */ 106 const struct iphdr *iph = ip_hdr(skb); 107 u_int32_t ip, i, j; 108 109 if (info->flags & XT_GEOIP_SRC) 110 ip = ntohl(iph->saddr); 111 else 112 ip = ntohl(iph->daddr); 113 114 spin_lock_bh(&geoip_lock); 115 for (i = 0; i < info->count; i++) { 116 if ((node = info->mem[i]) == NULL) { 117 printk(KERN_ERR "xt_geoip: what the hell ?? '%c%c' isn't loaded into memory... skip it!\n", 118 COUNTRY(info->cc[i])); 119 120 continue; 121 } 122 123 for (j = 0; j < node->count; j++) 124 if ((ip > node->subnets[j].begin) && (ip < node->subnets[j].end)) { 125 spin_unlock_bh(&geoip_lock); 126 return (info->flags & XT_GEOIP_INV) ? 0 : 1; 127 } 128 } 129 130 spin_unlock_bh(&geoip_lock); 131 return (info->flags & XT_GEOIP_INV) ? 1 : 0; 132} 133 134static int xt_geoip_mt_checkentry(const struct xt_mtchk_param *par) 135{ 136 137 struct xt_geoip_match_info *info = par->matchinfo; 138 struct geoip_info *node; 139 u_int8_t i; 140 141 /* FIXME: Call a function to free userspace allocated memory. 142 * As Martin J. said; this match might eat lot of memory 143 * if commited with iptables-restore --noflush 144 void (*gfree)(struct geoip_info *oldmem); 145 gfree = info->fini; 146 */ 147 148 /* If info->refcount isn't NULL, then 149 * it means that checkentry() already 150 * initialized this entry. Increase a 151 * refcount to prevent destroy() of 152 * this entry. */ 153 if (info->refcount != NULL) { 154 atomic_inc((atomic_t *)info->refcount); 155 return 0; 156 } 157 158 159 for (i = 0; i < info->count; i++) { 160 161 if ((node = find_node(info->cc[i])) != NULL) 162 atomic_inc((atomic_t *)&node->ref); //increase the reference 163 else 164 if ((node = add_node(info->mem[i])) == NULL) { 165 printk(KERN_ERR 166 "xt_geoip: unable to load '%c%c' into memory\n", 167 COUNTRY(info->cc[i])); 168 return -ENOMEM; 169 } 170 171 /* Free userspace allocated memory for that country. 172 * FIXME: It's a bit odd to call this function everytime 173 * we process a country. Would be nice to call 174 * it once after all countries've been processed. 175 * - SJ 176 * *not implemented for now* 177 gfree(info->mem[i]); 178 */ 179 180 /* Overwrite the now-useless pointer info->mem[i] with 181 * a pointer to the node's kernelspace structure. 182 * This avoids searching for a node in the match() and 183 * destroy() functions. 184 */ 185 info->mem[i] = node; 186 } 187 188 /* We allocate some memory and give info->refcount a pointer 189 * to this memory. This prevents checkentry() from increasing a refcount 190 * different from the one used by destroy(). 191 * For explanation, see http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html 192 */ 193 info->refcount = kmalloc(sizeof(u_int8_t), GFP_KERNEL); 194 if (info->refcount == NULL) { 195 printk(KERN_ERR "xt_geoip: failed to allocate `refcount' memory\n"); 196 return -ENOMEM; 197 } 198 *(info->refcount) = 1; 199 200 return 0; 201} 202 203static void xt_geoip_mt_destroy(const struct xt_mtdtor_param *par) 204{ 205 struct xt_geoip_match_info *info = par->matchinfo; 206 struct geoip_info *node; /* this keeps the code sexy */ 207 u_int8_t i; 208 209 /* Decrease the previously increased refcount in checkentry() 210 * If it's equal to 1, we know this entry is just moving 211 * but not removed. We simply return to avoid useless destroy() 212 * proce ssing. 213 */ 214 atomic_dec((atomic_t *)info->refcount); 215 if (*info->refcount) 216 return; 217 218 /* Don't leak my memory, you idiot. 219 * Bug found with nfsim.. the netfilter's best 220 * friend. --peejix */ 221 kfree(info->refcount); 222 223 /* This entry has been removed from the table so 224 * decrease the refcount of all countries it is 225 * using. 226 */ 227 228 for (i = 0; i < info->count; i++) 229 if ((node = info->mem[i]) != NULL) { 230 atomic_dec((atomic_t *)&node->ref); 231 232 /* Free up some memory if that node isn't used 233 * anymore. */ 234 if (node->ref < 1) 235 remove_node(node); 236 } 237 else 238 /* Something strange happened. There's no memory allocated for this 239 * country. Please send this bug to the mailing list. */ 240 printk(KERN_ERR 241 "xt_geoip: What happened peejix ? What happened acidfu ?\n" 242 "xt_geoip: please report this bug to the maintainers\n"); 243 return; 244} 245 246static struct xt_match xt_geoip_match __read_mostly = { 247 .family = NFPROTO_IPV4, 248 .name = "geoip", 249 .match = xt_geoip_mt, 250 .checkentry = xt_geoip_mt_checkentry, 251 .destroy = xt_geoip_mt_destroy, 252 .matchsize = sizeof(struct xt_geoip_match_info), 253 .me = THIS_MODULE, 254}; 255 256static int __init xt_geoip_mt_init(void) 257{ 258 return xt_register_match(&xt_geoip_match); 259} 260 261static void __exit xt_geoip_mt_fini(void) 262{ 263 xt_unregister_match(&xt_geoip_match); 264} 265 266module_init(xt_geoip_mt_init); 267module_exit(xt_geoip_mt_fini); 268