• 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. }  

  • 相关阅读:
    Hibernate关联映射
    mysql 外键约束
    巩固JavaSE基础--IDEA完成实战项目
    PHP--选择排序
    PHP--冒泡排序
    vscode调试单个PHP脚本文件
    Vscode下调试基于Homestead环境的Laravel框架
    Python Web开发
    Python之UDP编程
    Python之TCP编程
  • 原文地址:https://www.cnblogs.com/fengty90/p/3768876.html
Copyright © 2020-2023  润新知