来源: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网关好了,最后,性能因素,交给越来越好的硬件吧...
最终还是给出一个能跑的代码,先来展示一下实现的可行性吧,代码十分简单:
- #include <stdio.h>
- #include <string.h>
- #include <errno.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <linux/in.h>
- #include <linux/if_ether.h>
- #include <linux/if_packet.h>
- #include <net/if.h>
- #include <sys/ioctl.h>
- #include <ifaddrs.h>
- #define MAX_SIZE 4096
- struct iphdr {
- __u8 ihl:4,
- version:4;
- __u8 tos;
- __be16 tot_len;
- __be16 id;
- __be16 frag_off;
- __u8 ttl;
- __u8 protocol;
- __sum16 check;
- __be32 saddr;
- __be32 daddr;
- };
- #define ETHER_HEADER_LEN 14
- #define ICMP_PROTO 1
- struct match {
- __be32 saddr; //匹配源IP地址
- __be32 daddr; //匹配目标IP地址
- __be32 t_saddr; //修改成的源IP地址
- __be32 t_daddr; //修改后的目标IP地址
- int opt; //SNAT=1/DNAT=0
- };
- static u_int16_t checksum(u_int32_t init, u_int8_t *addr, size_t count)
- {
- u_int32_t sum = init;
- while( count > 1 ) {
- sum += ntohs(* (u_int16_t*) addr);
- addr += 2;
- count -= 2;
- }
- if( count > 0 )
- sum += * (u_int8_t *) addr;
- while (sum>>16)
- sum = (sum & 0xffff) + (sum >> 16);
- return (u_int16_t)~sum;
- }
- static u_int16_t ip_checksum(struct iphdr* iphdrp)
- {
- return checksum(0, (u_int8_t*)iphdrp, iphdrp->ihl<<2);
- }
- static void set_ip_checksum(struct iphdr* iphdrp)
- {
- iphdrp->check = 0;
- iphdrp->check = htons(checksum(0, (u_int8_t*)iphdrp, iphdrp->ihl<<2));
- }
- int main(int argc, char **argv) {
- int sock, sd_lan ;
- char buffer[2048];
- struct ifreq ethreq;
- struct sockaddr_ll sa;
- struct ifaddrs *ifa = NULL;
- struct match mt = {0};
- mt.saddr = inet_addr(argv[1]);
- mt.daddr = inet_addr(argv[2]);
- mt.t_saddr = inet_addr(argv[3]);
- mt.t_daddr = inet_addr(argv[4]);
- mt.opt = atoi(argv[5]);
- ock=socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
- strncpy(ethreq.ifr_name,"eth0",IFNAMSIZ);
- ioctl(sock, SIOCGIFFLAGS, ðreq);
- ethreq.ifr_flags|=IFF_PROMISC;
- ioctl(sock,SIOCSIFFLAGS, ðreq);
- memset( &sa, 0, sizeof(sa) );
- sa.sll_family = AF_PACKET;
- sa.sll_protocol = htons(ETH_P_IP);
- sa.sll_ifindex = if_nametoindex("eth0");
- bind(sock, (struct sockaddr*)&sa, sizeof(sa));
- getifaddrs(&ifa);
- while (1) {
- ssize_t len = 0;
- struct ethhdr *eh = NULL;
- struct iphdr *ip_header = NULL;
- len = recvfrom(sock,buffer, MAX_SIZE, 0, NULL, NULL);
- //ETH (Bridge ?)
- eh = (struct ethhdr*)buffer;
- //暂且写死了这个目标MAC,实际上应该从内核arp表获取的
- char dst_mac[ETH_ALEN] = {0x00,0x0c,0x29,0x90,0x66,0xcf};
- memcpy(eh->h_dest, dst_mac, ETH_ALEN);
- //设置源MAC的时候,注意要越过loopback口
- memcpy(eh->h_source, ((struct sockaddr_ll*)ifa->ifa_next->ifa_addr)->sll_addr, ETH_ALEN );
- ip_header = (struct iphdr*)(buffer + ETHER_HEADER_LEN);
- //作为一个例子只是针对ICMP
- if (ip_header->protocol != ICMP_PROTO) {
- continue;
- }
- if (ip_header->daddr == mt.daddr &&
- ip_header->saddr == mt.saddr) {
- if (mt.opt == 1)
- ip_header->saddr = mt.t_saddr;
- else
- ip_header->daddr = mt.t_daddr;
- }
- //重新计算校验码
- ip_header->check = htons(checksum(0, (u_int8_t*)ip_header, ip_header->ihl<<2));
- len = send(sock, buffer, len, 0);
- }
- }