PACKET套接字在用户态实现跨OS跨协议的NAT

来源:http://blog.csdn.net/dog250/article/details/7830274?reload


一般而言,NAT功能需要操作系统内核协议栈的支持,并且在用户态的配置还很不一样,如果能在用户态实现一个通用的NAT软件,那就再好不过了,由于库函数的跨平台特性,那么这种NAT也将会是跨平台的。那么需要做的工作就是抽出各个OS中共用的网络库的支持,最简单也是最显然的就是PACKET套接字了,因为NAT的目的就是修改IP地址,本质上就是修改IP数据包然而重放之,既然要修改,起码我们要先拿到这个IP数据包,也就是完全截获它,而不仅仅是监听它。
        第一步就是禁用OS的路由功能,在Linux中就是将ip_forward设置为0,这样数据包就不会溜走了;第二步就是用PACKET套接字将本要通过路由溜走的数据报给截获到用户态;第三步就是修改它的IP地址信息(或许还有端口信息?这没关系,包已经拿到了,怎么改随你!);第四步就是将修改后的数据包再通过PACKET套接字发出去。以上的步骤中,需要注意的有以下几点:
1.修改后的数据包的校验码需要重新计算,而这并不难,因为校验码的计算算法都是公开的;
2.由于PACKET套接字需要你完全构造整个数据包,包括以太头,那么源和目的MAC地址如何填写将是一个问题,我是这么做的:
2.1.源MAC地址修改成发出包的那个网口的MAC地址;
2.2.目标MAC地址要分类讨论,如果目标是直连网段,那么就需要首先在用户态arp一下目标或者是直接从内核取出arp信息;如果目标不是直连网段的,需要将目标MAC填充成到达目标的网关下一跳的MAC地址,这就需要一次路由查找过程以及一次arp获取过程,而这都不难,在Linux上可以通过netlink套接字得到,Windows平台也有相关的API。
以上就是全部的过程了。

        实际上,ip_queue也可以实现这个,然而ip_queue是内嵌于Linux的Netfilter框架内部的,脱离了Linux将很难移植到其它的OS,即便都在Linux上,如果没有Netfilter的支持也不行,因此PACKET套接字将是一种更加通用的方式实现NAT(或者VPN,总而言之都是修改原始的IP数据包)。
        另外,如果能搭配BPF(伯克利包过滤)就更好了,tcpdump就是使用BPF来设置到底哪些包需要抓取而哪些包不需要抓取的。本文没有使用BPF,而是一下子将所有的包都抓过来。
        这种用户态的实现是跨平台的,因为几乎所有的OS都有对PACKET套接字的支持,并且如果说你觉得想支持IPv6协议的话,改起来也比较简单,还是上述几个步骤,只是需要你对协议头有不太深入的理解即可。最终我觉得这种实现有个副作用,那就是必须关掉路由功能,这样那些不需要NAT的数据包也过不去了,但是还能怎样呢,任何事情都不是免费的啊,姑且用这个做一个单纯的NAT网关好了,最后,性能因素,交给越来越好的硬件吧...
        最终还是给出一个能跑的代码,先来展示一下实现的可行性吧,代码十分简单:

[plain] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <errno.h>    
  4. #include <unistd.h>  
  5. #include <sys/socket.h>  
  6. #include <sys/types.h>    
  7. #include <linux/in.h>  
  8. #include <linux/if_ether.h>  
  9. #include <linux/if_packet.h>  
  10. #include <net/if.h>  
  11. #include <sys/ioctl.h>  
  12. #include <ifaddrs.h>  
  13.   
  14. #define MAX_SIZE    4096  
  15. struct iphdr {  
  16.     __u8    ihl:4,  
  17.         version:4;  
  18.     __u8    tos;  
  19.     __be16    tot_len;  
  20.     __be16    id;  
  21.     __be16    frag_off;  
  22.     __u8    ttl;  
  23.     __u8    protocol;  
  24.     __sum16    check;  
  25.     __be32    saddr;  
  26.     __be32    daddr;  
  27. };  
  28.   
  29.   
  30. #define ETHER_HEADER_LEN    14  
  31. #define ICMP_PROTO        1  
  32.   
  33. struct match {  
  34.     __be32    saddr;        //匹配源IP地址  
  35.     __be32    daddr;        //匹配目标IP地址  
  36.     __be32    t_saddr;    //修改成的源IP地址  
  37.     __be32    t_daddr;    //修改后的目标IP地址  
  38.     int    opt;        //SNAT=1/DNAT=0  
  39. };  
  40.   
  41.   
  42. static u_int16_t checksum(u_int32_t init, u_int8_t *addr, size_t count)  
  43. {  
  44.     u_int32_t sum = init;  
  45.     while( count > 1 ) {  
  46.         sum += ntohs(* (u_int16_t*) addr);  
  47.         addr += 2;  
  48.         count -= 2;  
  49.     }  
  50.     if( count > 0 )  
  51.         sum += * (u_int8_t *) addr;  
  52.     while (sum>>16)  
  53.         sum = (sum & 0xffff) + (sum >> 16);  
  54.     return (u_int16_t)~sum;  
  55. }  
  56.   
  57. static u_int16_t ip_checksum(struct iphdr* iphdrp)  
  58. {  
  59.     return checksum(0, (u_int8_t*)iphdrp, iphdrp->ihl<<2);  
  60. }  
  61.   
  62. static void set_ip_checksum(struct iphdr* iphdrp)  
  63. {  
  64.     iphdrp->check = 0;  
  65.     iphdrp->check = htons(checksum(0, (u_int8_t*)iphdrp, iphdrp->ihl<<2));  
  66. }  
  67.   
  68. int main(int argc, char **argv) {  
  69.     int sock, sd_lan ;  
  70.     char buffer[2048];  
  71.     struct ifreq ethreq;  
  72.     struct sockaddr_ll sa;      
  73.     struct ifaddrs *ifa = NULL;  
  74.       
  75.     struct match mt = {0};  
  76.     mt.saddr = inet_addr(argv[1]);  
  77.     mt.daddr = inet_addr(argv[2]);  
  78.     mt.t_saddr = inet_addr(argv[3]);  
  79.     mt.t_daddr = inet_addr(argv[4]);  
  80.     mt.opt = atoi(argv[5]);  
  81.    
  82.     ock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));  
  83.     strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);  
  84.     ioctl(sock, SIOCGIFFLAGS, ðreq);  
  85.     ethreq.ifr_flags|=IFF_PROMISC;  
  86.     ioctl(sock,SIOCSIFFLAGS, ðreq);  
  87.     memset( &sa, 0, sizeof(sa) );  
  88.     sa.sll_family = AF_PACKET;  
  89.     sa.sll_protocol = htons(ETH_P_IP);  
  90.   
  91.     sa.sll_ifindex = if_nametoindex("eth0");  
  92.     bind(sock, (struct sockaddr*)&sa, sizeof(sa));  
  93.     getifaddrs(&ifa);  
  94.     while (1) {  
  95.         ssize_t len = 0;  
  96.         struct ethhdr *eh = NULL;  
  97.         struct iphdr *ip_header = NULL;  
  98.           
  99.         len = recvfrom(sock,buffer, MAX_SIZE, 0, NULL, NULL);  
  100.         //ETH (Bridge ?)  
  101.         eh = (struct ethhdr*)buffer;  
  102.         //暂且写死了这个目标MAC,实际上应该从内核arp表获取的  
  103.         char dst_mac[ETH_ALEN] = {0x00,0x0c,0x29,0x90,0x66,0xcf};  
  104.         memcpy(eh->h_dest, dst_mac, ETH_ALEN);  
  105.         //设置源MAC的时候,注意要越过loopback口  
  106.         memcpy(eh->h_source, ((struct sockaddr_ll*)ifa->ifa_next->ifa_addr)->sll_addr, ETH_ALEN );  
  107.              ip_header = (struct iphdr*)(buffer + ETHER_HEADER_LEN);  
  108.         //作为一个例子只是针对ICMP  
  109.         if (ip_header->protocol != ICMP_PROTO) {  
  110.             continue;  
  111.         }  
  112.         if (ip_header->daddr == mt.daddr &&  
  113.             ip_header->saddr == mt.saddr) {  
  114.             if (mt.opt == 1)  
  115.                 ip_header->saddr = mt.t_saddr;  
  116.             else  
  117.                 ip_header->daddr = mt.t_daddr;  
  118.         }  
  119.         //重新计算校验码  
  120.         ip_header->check = htons(checksum(0, (u_int8_t*)ip_header, ip_header->ihl<<2));      
  121.         len = send(sock, buffer, len, 0);  
  122.        }  
  123.    
  124. }  

原文地址:https://www.cnblogs.com/fengty90/p/3768876.html