首先在开篇之前介绍下内网打洞原理

场景:一个服务器S1在公网上有一个IP,两个私网机器C1,C2

C1,C2分别由NAT1和NAT2连接到公网,我们需要借助S1将C1,C2建立直接的TCP连接,即由C1向C2打一个洞,让C2可以沿这个洞直接连接到C1主机,也就成了局域网访问的模式。

实现过程如下:

  1. S1启动两个网络监听(主连接监听,打洞监听)
  2. 由于S1是公网,所以C1,C2和S1保持通信,
  3. 当C1需要和C2建立直接的TCP连接时,首先连接S1的打洞监听端口,并发给S1请求协助连接C2的申请,同时在该端口号上启动侦听,记得套接字设置允许重入SO_REUSEADDR 属性,否则侦听会失败
  4. S1监听打洞端口收到请求后通知C2,并将C1经过NAT1转换的公网IP地址和端口等信息告诉C2
  5. C2收到S1的连接通知后首先与S1的打洞端口连接,随便发送一些数据后立即断开(原因:让S1知道C2经过NAT-2转换后的公网IP和端口号)
  6. C2试着连接到C1(经过NAT1转换后的公网IP地址和端口),大多数路由器对于不请自到的SYN请求包直接丢弃而导致连接失败,但NAT1会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即C2向C1打了一个洞,下次C1就能直接连接到C2刚才使用的端口号
  7. 客户端C2打洞的同时在相同的端口上启动侦听。C2在一切准备就绪以后通过与S1的主连接监听端口回复消息“我准备好了”,S1在收到以后将C2经过NAT2转换后的公网IP和端口号告诉给C1
  8. 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外网访问的部署。同时基于代码量很小,实现很巧妙,对其源码进行了初步阅读。

对几个核心点进行记录

文件功能

  1. edge.c:客户端实现
  2. supernode.c:服务器端实现
  3. minilzo.c:数据压缩处理
  4. n2n.c:common函数实现
  5. tuntap_linux.c:tun/tap设备实现
  6. twofish.c:twofish加密算法的实现

数据结构

  1. typedef struct tuntap_dev {
  2. int fd;
  3. u_int8_t mac_addr[6];//MAC地址
  4. u_int32_t ip_addr, device_mask;//IP地址与子网掩码
  5. u_int mtu;//mtu值
  6. } tuntap_dev;//定义虚拟网卡的数据结构
  7.  
  8. enum packet_type {
  9. packet_unreliable_data = 0, /* no ACK needed */
  10. packet_reliable_data, /* needs ACK */
  11. packet_ping,
  12. packet_pong
  13. };//定义数据包的类型
  14.  
  15. struct peer_addr {
  16. u_int8_t family;
  17. u_int16_t port;
  18. union {
  19. u_int8_t v6_addr[16];
  20. u_int32_t v4_addr;
  21. } addr_type;
  22. };//定义节点地址端口信息数据结构,预留了IPv6
  23.  
  24. struct n2n_packet_header {
  25. u_int8_t version, msg_type, ttl, sent_by_supernode;
  26. //版本号、消息类型、(ttl还有待查明)、服务器节点转发标示位
  27. char community_name[COMMUNITY_LEN], src_mac[6], dst_mac[6];
  28. //客户所处子网社区名称、源MAC、目的MAC
  29. struct peer_addr public_ip, private_ip;
  30. //节点公网地址端口、私网地址端口信息
  31. enum packet_type pkt_type;//数据包类型
  32. u_int32_t sequence_id;//序列号
  33. u_int32_t crc; // 校验位,用来区分伪造数据包
  34. };//n2n数据包头信息
  35.  
  36. struct peer_info {
  37. char community_name[16], mac_addr[6];//子网社区名、MAC地址
  38. struct peer_addr public_ip, private_ip;//公网地址端口、私网地址端口
  39. time_t last_seen;//时间信息
  40. struct peer_info *next;//下一个节点
  41. /* socket */
  42. n2n_sock_info_t sinfo;//sock信息
  43. };//节点信息
  44.  
  45. struct n2n_edge
  46. {
  47. u_char re_resolve_supernode_ip;
  48. struct peer_addr supernode;//服务器地址端口信息
  49. char supernode_ip[48];//服务器IP地址
  50. char * community_name;//子网社区名,默认为NULL
  51.  
  52. n2n_sock_info_t sinfo;//sock信息
  53. u_int pkt_sent;//标示位,具体含义待查.默认为0
  54. tuntap_dev device;//虚拟网卡设备
  55. int allow_routing;//路由转发标示位,默认为0
  56. int drop_ipv6_ndp;//标示位,具体含义待查.默认为0
  57. char * encrypt_key;//加密密钥
  58. TWOFISH * tf;
  59. struct peer_info * known_peers /* = NULL*/;
  60. struct peer_info * pending_peers /* = NULL*/;
  61. time_t last_register /* = 0*/;
  62. };//客户节点数据结构,最重要的数据结构

发送Gratuitous ARP广播

  1. 使用:
  2. static void send_grat_arps(n2n_edge_t * eee,) {
  3. char buffer[48];
  4. size_t len;
  5.  
  6. traceEvent(TRACE_NORMAL, "Sending gratuitous ARP...");
  7. len = build_gratuitous_arp(buffer, sizeof(buffer));
  8. send_packet2net(eee, buffer, len);
  9. send_packet2net(eee, buffer, len); /* Two is better than one :-) */
  10. }
  11. ----包体定义-------
  12. static char gratuitous_arp[] = {
  13. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* Dest mac */
  14. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Src mac idx:6*/
  15. 0x08, 0x06, /* ARP */
  16. 0x00, 0x01, /* Ethernet */
  17. 0x08, 0x00, /* IP */
  18. 0x06, /* Hw Size */
  19. 0x04, /* Protocol Size */
  20. 0x00, 0x01, /* ARP Request */
  21. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Src mac idx:22*/
  22. 0x00, 0x00, 0x00, 0x00, /* Src IP idx:28*/
  23. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Target mac */
  24. 0x00, 0x00, 0x00, 0x00 /* Target IP idx:38*/
  25. };
  26.  
  27. static int build_gratuitous_arp(char *buffer, u_short buffer_len) {
  28. if(buffer_len < sizeof(gratuitous_arp)) return(-1);
  29.  
  30. memcpy(buffer, gratuitous_arp, sizeof(gratuitous_arp));
  31. memcpy(&buffer[6], device.mac_addr, 6);
  32. memcpy(&buffer[22], device.mac_addr, 6);
  33. memcpy(&buffer[28], &device.ip_addr, 4);
  34.  
  35. /* REVISIT: BbMaj7 - use a real netmask here. This is valid only by accident
  36. * for /24 IPv4 networks. */
  37. buffer[31] = 0xFF; /* Use a faked broadcast address */
  38. memcpy(&buffer[38], &device.ip_addr, 4);
  39. return(sizeof(gratuitous_arp));
  40. }

int转ip地址

  1. char* intoa(u_int32_t /* host order */ addr, char* buf, u_short buf_len) {
  2. char *cp, *retStr;
  3. u_int byte;
  4. int n;
  5. /*
  6. addr=268435456
  7. >>>268435456&255=0
  8. //右移8位
  9. >>>1048576&255=0
  10. //右移8位
  11. >>>4096&255=0
  12. //右移8位
  13. >>>16&255|16
  14. */
  15. printf(">>>%d|%d\n",addr,addr&255);
  16. addr >>= 8;
  17. printf(">>>%d|%d\n",addr,addr&255);
  18. addr >>= 8;
  19. printf(">>>%d|%d\n",addr,addr&255);
  20. addr >>= 8;
  21. printf(">>>%d|%d\n",addr,addr&255);
  22. cp = &buf[buf_len];
  23. *--cp = '\0';
  24. n = 4;
  25. do {
  26. //0xff=255
  27. byte = addr & 0xff;
  28. *--cp = byte % 10 + '0';
  29. byte /= 10;
  30. if (byte > 0) {
  31. *--cp = byte % 10 + '0';
  32. byte /= 10;
  33. if (byte > 0)
  34. *--cp = byte + '0';
  35. }
  36. *--cp = '.';
  37. addr >>= 8;
  38. } while (--n > 0);
  39.  
  40. /* Convert the string to lowercase */
  41. retStr = (char*)(cp+1);
  42.  
  43. return(retStr);
  44. }
  1. linux创建虚拟网卡
  1. tunctl -t tun0
  2. tunctl -t tun1
  3. ifconfig tun0 1.2.3.4 up
  4. ifconfig tun1 1.2.3.5 up
  5. ./edge -d tun0 -l 2000 -r 127.0.0.1:3000 -c hello
  6. ./edge -d tun1 -l 3000 -r 127.0.0.1:2000 -c hello
  7. tunctl -u UID -t tunX
  1. -----ip4int32 int32ip4----
  1. char *ip_str = "111.0.0.8";
  2. u_int32_t ip = inet_addr(ip_str);
  3. printf(">> ip:%d\n",ip);
  4. struct in_addr addr1;
  5. memcpy(&addr1, &ip, 4);
  6. printf(">> ip4_s:%s\n",inet_ntoa(addr1));

其他待补充

p2p技术之n2n源码核心简单分析一的更多相关文章

  1. MyBatis源码 核心配置解析 properties元素

    XMLConfigBuilder的parseConfiguration(XNode)方法,用于解析配置文件 XMLConfigBuilder的propertiesElement(XNode)方法,用于 ...

  2. 第九节:从源码的角度分析MVC中的一些特性及其用法

    一. 前世今生 乍眼一看,该标题写的有点煽情,最近也是在不断反思,怎么能把博客写好,让人能读下去,通俗易懂,深入浅出. 接下来几个章节都是围绕框架本身提供特性展开,有MVC程序集提供的,也有其它程序集 ...

  3. HTTP请求库——axios源码阅读与分析

    概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...

  4. 如何实现一个HTTP请求库——axios源码阅读与分析 JavaScript

    概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...

  5. 从源码的角度分析ViewGruop的事件分发

    从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...

  6. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  7. qt creator源码全方面分析(3-3)

    目录 qtcreatordata.pri 定义stripStaticBase替换函数 设置自定义编译和安装 QMAKE_EXTRA_COMPILERS Adding Compilers 示例1 示例2 ...

  8. 安卓图表引擎AChartEngine(二) - 示例源码概述和分析

    首先看一下示例中类之间的关系: 1. ChartDemo这个类是整个应用程序的入口,运行之后的效果显示一个list. 2. IDemoChart接口,这个接口定义了三个方法, getName()返回值 ...

  9. 通过官方API结合源码,如何分析程序流程

    通过官方API结合源码,如何分析程序流程通过官方API找到我们关注的API的某个方法,然后把整个流程执行起来,然后在idea中,把我们关注的方法打上断点,然后通过Step Out,从内向外一层一层分析 ...

随机推荐

  1. Handle/Looper源码分析;

    1. Handle中的属性: final Looper mLooper; final MessageQueue mQueue; final Callback mCallback; final bool ...

  2. js 序列化

    Python 序列化 字符串 = json.dumps(对象)  对象转字符串 对象 = json.loads(字符串)   字符串转对象 Javascript 字符串 = JSON.stringif ...

  3. Mac系统如何显示隐藏文件?

    显示全部文件 defaults write com.apple.finder AppleShowAllFiles -bool true osascript -e 'tell application & ...

  4. jsp页面选择文件上传,获取不到绝对路径问题

    选择"D:\\temp\file\test.txt"文件,alert(filename)却是"C:\\fakepath\test.txt" 出现D:\\temp ...

  5. cept源代码目录结构详解_知识树(转)

    1 简介该代码架构基于版本10.0.5整理,先整理根目录里的代码,再整理出src目录的架构. 2 代码架构2.1 Ceph源码根目录Ceph的根目录下包含了一些文件夹和若干编译.代码格式相关的文件. ...

  6. tcp那个孤独的小包到底怎么回事?

    内核3.10,接<tcp的发送端一个小包就能打破对端的delay_ack么?> 我们继续来分析这个没满mss的小包, 可以看到,由于受到syn ack这端是发包端,所以该发送链路协商的ms ...

  7. 远程服务器设置Mysql的操作权限

    mysql -u root -p; root用户输入密码登录mysql服务器 select host, user  from mysql.user; 查询数据库的所有用户以前权限的ip   host: ...

  8. gson格式化参数 对象转Map

    前台传json到后台接收: String  params = request.getParameters("paramtes"); Map<String, Map<St ...

  9. MVC ScriptBundle自定义排序。

    今天发现MVC的ScriptBundle @Scripts.Render()后是按照我也不知道顺序显示在页面上的,后果就是jquery.min.js被排在了后面(反正我下面那堆默认jquery.min ...

  10. Unity3D人脸建模 AvataSDK研究

    1.Unity与windows交互 调用文件浏览器 1.用C#调用comdlg32.dll  ,  利用GetOpenFileName实现打开文件对话框 <1> 整体参考https://w ...