p2p技术之n2n源码核心简单分析一
首先在开篇之前介绍下内网打洞原理
场景:一个服务器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));
其他待补充
p2p技术之n2n源码核心简单分析一的更多相关文章
- MyBatis源码 核心配置解析 properties元素
XMLConfigBuilder的parseConfiguration(XNode)方法,用于解析配置文件 XMLConfigBuilder的propertiesElement(XNode)方法,用于 ...
- 第九节:从源码的角度分析MVC中的一些特性及其用法
一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...
- HTTP请求库——axios源码阅读与分析
概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...
- 如何实现一个HTTP请求库——axios源码阅读与分析 JavaScript
概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- qt creator源码全方面分析(3-3)
目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...
- 安卓图表引擎AChartEngine(二) - 示例源码概述和分析
首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...
- 通过官方API结合源码,如何分析程序流程
通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...
随机推荐
- 虚拟机Ubuntu18.04——gcc版本的升降
致读者:这是本人第一篇博客,小试牛刀,希望能在以后的道路中分享出更多实用的技巧和知识,大家一起进步. 操作环境: VMware Workstation 14Pro .64位Ubuntu18.04系统 ...
- vue+sass实现切换字体大小
接到领导指示,用户嫌我做的页面字体太小,15px的字体叫小?领导说用户多是上了年纪的人.没办法,改吧,谁让咱是个搬砖的呢..咳咳 我寻思着这次改大了,下次用户嫌大再让改小呢?干脆给他做个选择字号的功能 ...
- 从 注解和继承 到 JAXB中的注意事项
从 注解和继承 到 JAXB中的注意事项 注解在继承中的行为 如果一个父类添加了一个注解,子类是否能取到这个注解呢?如下 package inheritance; import java.lang.a ...
- 原生js开发vue的双向数据绑定
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 关于总结一些CentOS7常用的运维命令
Centos7日常需要用的运维命令 1.删除0字节文件 find-type f -size 0 -exec rm -rf {} \ 2.查看进程 按内存从大到小排列 ps -e -o " ...
- 贝叶斯公式与最大后验估计(MAP)
1, 频率派思想 频率派思想认为概率乃事情发生的频率,概率是一固定常量,是固定不变的 2, 最大似然估计 假设有100个水果由苹果和梨混在一起,具体分配比例未知,于是你去随机抽取10次,抽到苹果标记为 ...
- windows安装composer
Composer 是 PHP 的一个依赖管理工具(不是一个包管理器).它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们. 在windows下安装的方法 方法一:使用安装程序 这是将 Com ...
- NetworkStream介绍说明
如果服务器和客户端之间基于TCP连接的,他们之间能够依靠一个稳定的字节流进行相互传输信息,这也是NetworkStream的最关键的作用,有了这个神奇的协议,NetWorkStream便能向其他流一样 ...
- 好用的截图picpick工具,无需注册,无需破解
链接:https://pan.baidu.com/s/1KtgF2wPdbRXAAenvrPiPzA 提取码:vasu
- 深入理解BERT Transformer ,不仅仅是注意力机制
来源商业新知网,原标题:深入理解BERT Transformer ,不仅仅是注意力机制 BERT是google最近提出的一个自然语言处理模型,它在许多任务 检测上表现非常好. 如:问答.自然语言推断和 ...