设为首页 收藏本站
查看: 881|回复: 0

[经验分享] PACKET套接字在用户态实现跨OS跨协议的NAT

[复制链接]

尚未签到

发表于 2016-5-18 10:37:30 | 显示全部楼层 |阅读模式
一般而言,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);
}
}

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-218619-1-1.html 上篇帖子: java获取网卡MAC地址,java获取网卡序列号的方法 下篇帖子: mac上安装和简单的使用MongoDB
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表