├── Makefile ├── README └── knat.c /Makefile: -------------------------------------------------------------------------------- 1 | obj-m += knat.o 2 | 3 | all: 4 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 5 | 6 | clean: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | *****NAT Kernel Module***** 2 | ******************************* 3 | Author: Gaurav Tungatkar 4 | 5 | Description: 6 | Linu kernel module that does network address translation. WAN side IP address, 7 | LAN subnet and NAT entry timeout are configurable parameters via procfs. 8 | Start stop using sysfs. 9 | 10 | Simple timeout strategy to remove NAT table entries, does not do connection 11 | tracking. 12 | 13 | Pre-routing and Post routing netfilter hooks to change the IP addresses and 14 | recalculate checksums. 15 | 16 | Instructions to compile: 17 | 1. Go to project/ folder. 18 | 2. make 19 | 20 | Instructions to run: 21 | 1. insmod project/knat.ko 22 | 23 | 24 | Limitations: 25 | 1. Limitations: Port numbers to be used in translation are taken by incrementing a global variable. 26 | 2. If timeout is specified very less, and timeout occurs during an existing TCP connection, that connection will break, since NAT table entry will be removed. 27 | 3. Does not support FTP. 28 | -------------------------------------------------------------------------------- /knat.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #define MAX_NAT_ENTRIES 65535 18 | #define SET_ENTRY 133 19 | #define RWPERM 0644 20 | 21 | /* NAT table entry*/ 22 | struct nat_entry { 23 | __be32 lan_ipaddr; 24 | __be16 lan_port; 25 | // __be16 nat_port; 26 | unsigned long sec; /*timestamp in seconds*/ 27 | u_int8_t valid; 28 | }; 29 | 30 | /*the NAT table is indexed by the translated port i.e. source port after NAT for outgoing packet*/ 31 | static struct nat_entry nat_table[MAX_NAT_ENTRIES]; 32 | 33 | static __be32 myip; 34 | static __be32 priv_ip_mask; 35 | static __be32 priv_ip_first; 36 | static int start = 0; 37 | static int timeout = 60; 38 | static char lanstr[20] = "192.168.56.0/24"; 39 | static u_int16_t port = 10000; 40 | module_param(start, int, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); 41 | 42 | /*proc fs entries */ 43 | static struct proc_dir_entry *knat; 44 | static struct proc_dir_entry *proc_ip, *proc_lan, *proc_timeout; 45 | 46 | /*helper routines for IP address conversion*/ 47 | unsigned long ip_asc_to_int(char *strip) 48 | { 49 | unsigned long ip; 50 | unsigned int a[4]; 51 | 52 | sscanf(strip, "%u.%u.%u.%u", &a[0], &a[1], &a[2], &a[3]); 53 | ip = (a[0] << 24)+(a[1] << 16)+(a[2] << 8)+a[3] ; 54 | return ip; 55 | } 56 | 57 | void inet_ntoa(char *tmp, u_int32_t int_ip) 58 | { 59 | 60 | sprintf(tmp, "%d.%d.%d.%d", (int_ip) & 0xFF, (int_ip >> 8 ) & 0xFF,(int_ip >> 16) & 0xFF,(int_ip >> 24) & 0xFF); 61 | return; 62 | } 63 | 64 | /*proc fs read write*/ 65 | 66 | static int proc_read_ip(char *page, char **start, 67 | off_t off, int count, 68 | int *eof, void *data) 69 | { 70 | char tmp[16]; 71 | int len; 72 | if(off > 0) 73 | { 74 | *eof = 1; 75 | return 0; 76 | } 77 | inet_ntoa(tmp, myip); 78 | len = sprintf(page, "%s\n", tmp); 79 | return len; 80 | } 81 | static int proc_write_ip(struct file *file, 82 | const char *buffer, 83 | unsigned long count, 84 | void *data) 85 | { 86 | 87 | char tmp[16]; 88 | if(count > 15) 89 | { 90 | //don't try to convert that string. 91 | return -ENOSPC; 92 | } 93 | 94 | if(copy_from_user(tmp, buffer, count)){ 95 | return -EFAULT; 96 | } 97 | tmp[count] = '\0'; 98 | myip = htonl(ip_asc_to_int(tmp)); 99 | return count; 100 | } 101 | static int proc_read_timeout(char *page, char **start, 102 | off_t off, int count, 103 | int *eof, void *data) 104 | { 105 | int len; 106 | if(off > 0) 107 | { 108 | *eof = 1; 109 | return 0; 110 | } 111 | len = sprintf(page, "%u\n", timeout); 112 | return len; 113 | } 114 | static int proc_write_timeout(struct file *file, 115 | const char *buffer, 116 | unsigned long count, 117 | void *data) 118 | { 119 | 120 | #define MAX_TIMEOUT_LEN_CHARS 6 121 | char tmp[10]; 122 | if(count > MAX_TIMEOUT_LEN_CHARS) 123 | { 124 | //don't try to convert that string. 125 | return -EFAULT; 126 | } 127 | 128 | if(copy_from_user(tmp, buffer, count)){ 129 | return -EFAULT; 130 | } 131 | tmp[count] = '\0'; 132 | timeout = simple_strtoul(tmp, NULL, 10); 133 | return count; 134 | } 135 | static int proc_read_lan(char *page, char **start, 136 | off_t off, int count, 137 | int *eof, void *data) 138 | { 139 | int len; 140 | if(off > 0) 141 | { 142 | *eof = 1; 143 | return 0; 144 | } 145 | len = sprintf(page, "%s\n", lanstr); 146 | return len; 147 | } 148 | static int proc_write_lan(struct file *file, 149 | const char *buffer, 150 | unsigned long count, 151 | void *data) 152 | { 153 | 154 | int mask, i; 155 | char tmp[20]; 156 | char *s; 157 | u_int32_t le_mask = 0; 158 | if(count > 20) 159 | { 160 | //don't try to convert that string. 161 | return -EFAULT; 162 | } 163 | 164 | if(copy_from_user(lanstr, buffer, count)){ 165 | return -EFAULT; 166 | } 167 | lanstr[count] = '\0'; 168 | strncpy(tmp, lanstr, count+1); 169 | s = strstr(tmp, "/"); 170 | if(s == NULL) 171 | { 172 | return -EFAULT; 173 | } 174 | *s = '\0'; 175 | s++; 176 | 177 | priv_ip_first = htonl(ip_asc_to_int(tmp)); 178 | mask = (simple_strtoul(s, NULL, 10)); 179 | for(i = 0; i < mask; i++) 180 | { 181 | le_mask = le_mask << 1; 182 | le_mask = le_mask | 1; 183 | } 184 | priv_ip_mask = le_mask; 185 | return count; 186 | } 187 | 188 | /* update the checksums for tcp and ip*/ 189 | void update_tcp_ip_checksum(struct sk_buff *skb, struct tcphdr *tcph, 190 | struct iphdr *iph) 191 | { 192 | 193 | int len; 194 | if (!skb || !iph || !tcph) return ; 195 | len = skb->len; 196 | 197 | /*update ip checksum*/ 198 | iph->check = 0; 199 | iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); 200 | /*update tcp checksum */ 201 | tcph->check = 0; 202 | tcph->check = tcp_v4_check( 203 | len - 4*iph->ihl, 204 | iph->saddr, iph->daddr, 205 | csum_partial((char *)tcph, len-4*iph->ihl, 206 | 0)); 207 | return; 208 | 209 | } 210 | 211 | /*find the nat table entry for given lan port. 212 | @sport = source port as obtained from packet from lan*/ 213 | __be16 find_nat_entry(__be32 saddr, __be16 sport) 214 | { 215 | int i = 0; 216 | unsigned int t = 0; 217 | for(i = 0; i < MAX_NAT_ENTRIES; i++) 218 | { 219 | if((nat_table[i].lan_ipaddr == saddr) && (nat_table[i].lan_port == sport) && nat_table[i].valid) 220 | { 221 | t = (get_seconds() - nat_table[i].sec); 222 | if(t > timeout) 223 | { 224 | printk("NAT Entry timeout\n"); 225 | nat_table[i].valid = 0; 226 | return 0; 227 | } 228 | return i; 229 | } 230 | } 231 | return 0; 232 | } 233 | static struct nf_hook_ops netfilter_ops_in, netfilter_ops_pre; 234 | 235 | /*PRE ROUTING Hook: In this we do DNAT 236 | For packets coming from WAN, destination IP and port are changed to lan ip and port from NAT Table entries */ 237 | 238 | unsigned int main_hook_pre(unsigned int hooknum, 239 | 240 | struct sk_buff *skb, 241 | 242 | const struct net_device *in, 243 | 244 | const struct net_device *out, 245 | 246 | int (*okfn)(struct sk_buff*)) 247 | 248 | { 249 | 250 | struct iphdr *iph; 251 | struct tcphdr *tcph; 252 | __be16 lan_port; 253 | 254 | 255 | if(start == 0) 256 | return NF_ACCEPT; 257 | if (!skb) return NF_ACCEPT; 258 | 259 | printk("PRE ROUTING"); 260 | 261 | 262 | iph = ip_hdr(skb); 263 | 264 | if (!iph) return NF_ACCEPT; 265 | 266 | 267 | if (iph->protocol==IPPROTO_TCP) 268 | { 269 | if(iph->daddr == myip) 270 | { 271 | tcph = (struct tcphdr*)((char *)iph + iph->ihl*4); 272 | if(!tcph) return NF_ACCEPT; 273 | if(nat_table[tcph->dest].valid == SET_ENTRY) 274 | { 275 | /*lazy checking of stale entries*/ 276 | if((get_seconds() - nat_table[tcph->dest].sec) > timeout) 277 | { 278 | /*stale entry which means we do not have a NAT entry for this packet*/ 279 | nat_table[tcph->dest].valid = 0; 280 | return NF_ACCEPT; 281 | } 282 | /*translate ip addr and port*/ 283 | lan_port = nat_table[tcph->dest].lan_port; 284 | iph->daddr = nat_table[tcph->dest].lan_ipaddr; 285 | tcph->dest = lan_port; 286 | //re-calculate checksum 287 | update_tcp_ip_checksum(skb, tcph, iph); 288 | } 289 | } 290 | } 291 | 292 | 293 | return NF_ACCEPT; 294 | 295 | } 296 | 297 | /*POST ROUTING hook: We do SNAT here. 298 | Packets from LAN - source IP and port are translated to public IP and sent out*/ 299 | 300 | unsigned int main_hook_post(unsigned int hooknum, 301 | 302 | struct sk_buff *skb, 303 | 304 | const struct net_device *in, 305 | 306 | const struct net_device *out, 307 | 308 | int (*okfn)(struct sk_buff*)) 309 | 310 | { 311 | 312 | struct iphdr *iph; 313 | struct tcphdr *tcph; 314 | __be32 oldip, newip; 315 | __be16 newport; 316 | int len = 0; 317 | 318 | if(start == 0) 319 | return NF_ACCEPT; 320 | if (!skb) return NF_ACCEPT; 321 | 322 | printk("POST ROUTING"); 323 | 324 | 325 | iph = ip_hdr(skb); 326 | len = skb->len; 327 | if (!iph) return NF_ACCEPT; 328 | 329 | 330 | if (iph->protocol==IPPROTO_TCP) 331 | { 332 | oldip = iph->saddr; 333 | /*Is this packet from given LAN range*/ 334 | if((oldip & priv_ip_mask) == priv_ip_first) 335 | { 336 | tcph = (struct tcphdr*)((char *)iph + iph->ihl*4); 337 | if(!tcph) return NF_ACCEPT; 338 | newport = find_nat_entry(iph->saddr, tcph->source); 339 | if(newport) 340 | { 341 | /*NAT entry already exists*/ 342 | tcph->source = newport; 343 | } 344 | else 345 | { 346 | /*Make a new NAT entry choose port numbers > 10000*/ 347 | newport = htons(port++); 348 | if(port == 0) port = 10000; 349 | nat_table[newport].valid = SET_ENTRY; 350 | nat_table[newport].lan_ipaddr = iph->saddr; 351 | nat_table[newport].lan_port = tcph->source; 352 | nat_table[newport].sec = get_seconds(); 353 | tcph->source = newport; 354 | 355 | } 356 | iph->saddr = myip; 357 | newip = iph->saddr; 358 | update_tcp_ip_checksum(skb, tcph, iph); 359 | } 360 | 361 | } 362 | 363 | 364 | return NF_ACCEPT; 365 | 366 | } 367 | 368 | 369 | 370 | static int __init init(void) 371 | 372 | { 373 | int mask = 24; 374 | int i = 0, rv = 0; 375 | u_int32_t le_mask = 0; 376 | for(i = 0; i < mask; i++) 377 | { 378 | le_mask = le_mask << 1; 379 | le_mask = le_mask | 1; 380 | } 381 | //le_mask = le_mask << zeroes; 382 | priv_ip_mask = le_mask; 383 | priv_ip_first = htonl(ip_asc_to_int("192.168.56.0")); 384 | myip = htonl(ip_asc_to_int("192.168.2.10")); 385 | netfilter_ops_in.hook = main_hook_post; 386 | 387 | netfilter_ops_in.pf = PF_INET; 388 | 389 | netfilter_ops_in.hooknum = NF_INET_POST_ROUTING; 390 | 391 | netfilter_ops_in.priority = NF_IP_PRI_FIRST; 392 | netfilter_ops_pre.hook = main_hook_pre; 393 | 394 | netfilter_ops_pre.pf = PF_INET; 395 | 396 | netfilter_ops_pre.hooknum = NF_INET_PRE_ROUTING; 397 | 398 | netfilter_ops_pre.priority = NF_IP_PRI_FIRST; 399 | 400 | knat = proc_mkdir("knat", NULL); 401 | if(knat == NULL){ 402 | rv = -ENOMEM; 403 | goto out; 404 | } 405 | 406 | proc_ip = create_proc_entry("ip", RWPERM, knat); 407 | if(proc_ip == NULL){ 408 | rv = -ENOMEM; 409 | goto out; 410 | } 411 | proc_ip->read_proc = proc_read_ip; 412 | proc_ip->write_proc = proc_write_ip; 413 | 414 | proc_timeout = create_proc_entry("timeout", RWPERM, knat); 415 | if(proc_timeout == NULL){ 416 | rv = -ENOMEM; 417 | goto out; 418 | } 419 | proc_timeout->read_proc = proc_read_timeout; 420 | proc_timeout->write_proc = proc_write_timeout; 421 | 422 | proc_lan = create_proc_entry("lan", RWPERM, knat); 423 | if(proc_lan == NULL){ 424 | rv = -ENOMEM; 425 | goto out; 426 | } 427 | proc_lan->read_proc = proc_read_lan; 428 | proc_lan->write_proc = proc_write_lan; 429 | 430 | nf_register_hook(&netfilter_ops_pre); 431 | nf_register_hook(&netfilter_ops_in); 432 | 433 | return 0; 434 | out: 435 | return rv; 436 | } 437 | 438 | 439 | 440 | static void __exit cleanup(void) 441 | { 442 | 443 | remove_proc_entry("ip", knat); 444 | remove_proc_entry("lan", knat); 445 | remove_proc_entry("timeout", knat); 446 | remove_proc_entry("knat", NULL); 447 | nf_unregister_hook(&netfilter_ops_in); 448 | nf_unregister_hook(&netfilter_ops_pre); 449 | 450 | } 451 | 452 | 453 | 454 | module_init(init); 455 | 456 | module_exit(cleanup); 457 | 458 | 459 | 460 | MODULE_LICENSE("GPL"); 461 | --------------------------------------------------------------------------------