首先在开篇之前介绍下内网打洞原理
场景:一个服务器S1在公网上有一个IP,两个私网机器C1,C2
C1,C2分别由NAT1和NAT2连接到公网,我们需要借助S1将C1,C2建立直接的TCP连接,即由C1向C2打一个洞,让C2可以沿这个洞直接连接到C1主机,也就成了局域网访问的模式。
实现过程如下:
- S1启动两个网络监听(主连接监听,打洞监听)
- 由于S1是公网,所以C1,C2和S1保持通信,
- 当C1需要和C2建立直接的TCP连接时,首先连接S1的打洞监听端口,并发给S1请求协助连接C2的申请,同时在该端口号上启动侦听,记得套接字设置允许重入SO_REUSEADDR 属性,否则侦听会失败
- S1监听打洞端口收到请求后通知C2,并将C1经过NAT1转换的公网IP地址和端口等信息告诉C2
- C2收到S1的连接通知后首先与S1的打洞端口连接,随便发送一些数据后立即断开(原因:让S1知道C2经过NAT-2转换后的公网IP和端口号)
- C2试着连接到C1(经过NAT1转换后的公网IP地址和端口),大多数路由器对于不请自到的SYN请求包直接丢弃而导致连接失败,但NAT1会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即C2向C1打了一个洞,下次C1就能直接连接到C2刚才使用的端口号
- 客户端C2打洞的同时在相同的端口上启动侦听。C2在一切准备就绪以后通过与S1的主连接监听端口回复消息“我准备好了”,S1在收到以后将C2经过NAT2转换后的公网IP和端口号告诉给C1
- C1收到S1回复的C2的公网IP和端口号等信息以后,开始连接到C2公网IP和端口号,由于在步骤6中C2曾经尝试连接过C1的公网IP地址和端口,NAT1纪录了此次连接的信息,所以当C1主动连接C2时,NAT2会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了
n2n项目开源地址:http://github.com/ntop/n2n
其实现核心是利用虚拟网卡巧妙实现了网络隧道的封装,只利用了tap设备,实用twofish加密接口lzo数据压缩实现了内网通讯。对于个人来说,非常适合建立家庭组网的远程访问和管理,本人就基于该方案实现了家中NAS外网访问的部署。同时基于代码量很小,实现很巧妙,对其源码进行了初步阅读。
对几个核心点进行记录
文件功能
-
edge.c:客户端实现
-
supernode.c:服务器端实现
-
minilzo.c:数据压缩处理
-
n2n.c:common函数实现
-
tuntap_linux.c:tun/tap设备实现
-
twofish.c:twofish加密算法的实现
数据结构
typedef struct tuntap_dev { int fd; u_int8_t mac_addr[6];//MAC地址 u_int32_t ip_addr, device_mask;//IP地址与子网掩码 u_int mtu;//mtu值 } tuntap_dev;//定义虚拟网卡的数据结构 enum packet_type { packet_unreliable_data = 0, /* no ACK needed */ packet_reliable_data, /* needs ACK */ packet_ping, packet_pong };//定义数据包的类型 struct peer_addr { u_int8_t family; u_int16_t port; union { u_int8_t v6_addr[16]; u_int32_t v4_addr; } addr_type; };//定义节点地址端口信息数据结构,预留了IPv6 struct n2n_packet_header { u_int8_t version, msg_type, ttl, sent_by_supernode; //版本号、消息类型、(ttl还有待查明)、服务器节点转发标示位 char community_name[COMMUNITY_LEN], src_mac[6], dst_mac[6]; //客户所处子网社区名称、源MAC、目的MAC struct peer_addr public_ip, private_ip; //节点公网地址端口、私网地址端口信息 enum packet_type pkt_type;//数据包类型 u_int32_t sequence_id;//序列号 u_int32_t crc; // 校验位,用来区分伪造数据包 };//n2n数据包头信息 struct peer_info { char community_name[16], mac_addr[6];//子网社区名、MAC地址 struct peer_addr public_ip, private_ip;//公网地址端口、私网地址端口 time_t last_seen;//时间信息 struct peer_info *next;//下一个节点 /* socket */ n2n_sock_info_t sinfo;//sock信息 };//节点信息 struct n2n_edge { u_char re_resolve_supernode_ip; struct peer_addr supernode;//服务器地址端口信息 char supernode_ip[48];//服务器IP地址 char * community_name;//子网社区名,默认为NULL n2n_sock_info_t sinfo;//sock信息 u_int pkt_sent;//标示位,具体含义待查.默认为0 tuntap_dev device;//虚拟网卡设备 int allow_routing;//路由转发标示位,默认为0 int drop_ipv6_ndp;//标示位,具体含义待查.默认为0 char * encrypt_key;//加密密钥 TWOFISH * tf; struct peer_info * known_peers /* = NULL*/; struct peer_info * pending_peers /* = NULL*/; time_t last_register /* = 0*/; };//客户节点数据结构,最重要的数据结构
发送Gratuitous ARP广播
使用: static void send_grat_arps(n2n_edge_t * eee,) { char buffer[48]; size_t len; traceEvent(TRACE_NORMAL, "Sending gratuitous ARP..."); len = build_gratuitous_arp(buffer, sizeof(buffer)); send_packet2net(eee, buffer, len); send_packet2net(eee, buffer, len); /* Two is better than one :-) */ } ----包体定义------- static char gratuitous_arp[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Dest mac */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Src mac idx:6*/ 0x08, 0x06, /* ARP */ 0x00, 0x01, /* Ethernet */ 0x08, 0x00, /* IP */ 0x06, /* Hw Size */ 0x04, /* Protocol Size */ 0x00, 0x01, /* ARP Request */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Src mac idx:22*/ 0x00, 0x00, 0x00, 0x00, /* Src IP idx:28*/ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Target mac */ 0x00, 0x00, 0x00, 0x00 /* Target IP idx:38*/ }; static int build_gratuitous_arp(char *buffer, u_short buffer_len) { if(buffer_len < sizeof(gratuitous_arp)) return(-1); memcpy(buffer, gratuitous_arp, sizeof(gratuitous_arp)); memcpy(&buffer[6], device.mac_addr, 6); memcpy(&buffer[22], device.mac_addr, 6); memcpy(&buffer[28], &device.ip_addr, 4); /* REVISIT: BbMaj7 - use a real netmask here. This is valid only by accident * for /24 IPv4 networks. */ buffer[31] = 0xFF; /* Use a faked broadcast address */ memcpy(&buffer[38], &device.ip_addr, 4); return(sizeof(gratuitous_arp)); }
int转ip地址
char* intoa(u_int32_t /* host order */ addr, char* buf, u_short buf_len) { char *cp, *retStr; u_int byte; int n; /* addr=268435456 >>>268435456&255=0 //右移8位 >>>1048576&255=0 //右移8位 >>>4096&255=0 //右移8位 >>>16&255|16 */ printf(">>>%d|%d\n",addr,addr&255); addr >>= 8; printf(">>>%d|%d\n",addr,addr&255); addr >>= 8; printf(">>>%d|%d\n",addr,addr&255); addr >>= 8; printf(">>>%d|%d\n",addr,addr&255); cp = &buf[buf_len]; *--cp = '\0'; n = 4; do { //0xff=255 byte = addr & 0xff; *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) { *--cp = byte % 10 + '0'; byte /= 10; if (byte > 0) *--cp = byte + '0'; } *--cp = '.'; addr >>= 8; } while (--n > 0); /* Convert the string to lowercase */ retStr = (char*)(cp+1); return(retStr); }
linux创建虚拟网卡
tunctl -t tun0 tunctl -t tun1 ifconfig tun0 1.2.3.4 up ifconfig tun1 1.2.3.5 up ./edge -d tun0 -l 2000 -r 127.0.0.1:3000 -c hello ./edge -d tun1 -l 3000 -r 127.0.0.1:2000 -c hello tunctl -u UID -t tunX
-----ip4转int32 及int32转ip4----
char *ip_str = "111.0.0.8"; u_int32_t ip = inet_addr(ip_str); printf(">> ip:%d\n",ip); struct in_addr addr1; memcpy(&addr1, &ip, 4); printf(">> ip4_s:%s\n",inet_ntoa(addr1));
其他待补充