n2n源码核心简单分析一

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

场景:一个服务器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加密算法的实现

数据结构

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));

其他待补充

原文地址:https://www.cnblogs.com/wyxy2005/p/10407075.html

时间: 2024-08-30 06:11:50

n2n源码核心简单分析一的相关文章

java线程池源码的简单分析

工作中用过线程池来进行多线程的操作,但是也仅仅是停留在使用方面,没有深入研究,现在通过源码来仔细研究下java的线程池.关于线程池的优缺点就不研究了,直接通过一个源码来看看java中线程池的原理. 使用ThreadPoolExecutor来创建一个线程池 public class MultipleThread { public static void main(String[] args) { /** * 通过ThreadPoolExecutor来创建一个线程池 * * 我们创建的线程池 核心线

String源码j简单分析

分析: 1. private final char value[]; String内部由这个char数组维护String的字符.首先String类用final修饰,不可继承,其次,value[]用 fianl修饰,代表引用不可变. public String() { this.value = new char[0]; } 当调用无参构造方法时,将char数组初始化为char[0]. 2. String中的codePoint codePoint  举例来说: "我"->对应的cod

从源码上,分析AsyncTask的实现

Android开发者们应该都知道AsyncTask这个类,它是系统提供的一个异步任务类,可以方便的让我们实现异步操作.在本篇文章中,我将带大家进入源码,简单分析一下AsyncTask的实现. 首先,贴上AsyncTask类的源码: package android.os; import java.util.ArrayDeque; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; impo

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

概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的一个HTTP请求库,目前在GitHub中已经拥有了超过40K的star,受到了各位大佬的推荐. 今天,我们就来看下,axios到底是如何设计的,其中又有哪些值得我们学习的地方.我在写这边文章时,axios的版本为0.18.0.我们就以这个版本的代码为例,来进行具体的源码阅读和分析.当前axios所有

从Handler+Message+Looper源码带你分析Android系统的消息处理机制

引言 [转载请注明出处:从Handler+Message+Looper源码带你分析Android系统的消息处理机制 CSDN 废墟的树] 作为Android开发者,相信很多人都使用过Android的Handler类来处理异步任务.那么Handler类是怎么构成一个异步任务处理机制的呢?这篇 博客带你从源码分析Android的消息循环处理机制,便于深入的理解. 这里不得不从"一个Bug引发的思考"开始研究Android的消息循环处理机制.说来话长,在某一次的项目中,原本打算开启一个工作线

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

从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是Android中所有布局的父类或间接父类,像LinearLayout.RelativeLayout等都是继承自ViewGroup的.但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能.ViewGroup继承结构示意图如

Chromium源码--视频播放流程分析(WebMediaPlayerImpl解析)

转载请注明出处:http://www.cnblogs.com/fangkm/p/3797278.html 承接上一篇文章.媒体播放,需要指定一个源文件,html5用URL格式来指定视频源文件地址,可以是http链接,也可以使本地源文件(不能直接指定,需要借助blob二进制类型).播放网络文件比播放本地文件多了个下载流程, 所以下面直接分析网络文件的播放流程,本地文件的播放流程也就清楚了.首先分析下网络视频资源的加载流程,相关结构图如下: WebMediaPlayerImpl类有一成员Buffer

CopyOnWriteArrayList 源码阅读与分析

CopyOnWriteArrayList 源码阅读与分析 CopyOnWriteArrayList是并发包下的一个线程安全.可以实现高并发的ArrayList 首先来看看它的构造方法: final void setArray(Object[] a) { array = a; } /** * Creates an empty list. */ public CopyOnWriteArrayList() { setArray(new Object[0]); } 可以看到,它和ArrayList有一定

qt creator源码全方面分析(2-0)

目录 Extending Qt Creator Manual 生成领域特定的代码和模板 代码片段 文件和项目模板 自定义向导 支持其他文件类型 MIME类型 高亮和缩进 自定义文本编辑器 其他自定义编辑器 运行外部工具 简单的外部工具 复杂的外部工具 所有主题 Extending Qt Creator Manual Qt Creator是为Qt开发人员的需求量身定制的跨平台集成开发环境(IDE). Qt Creator可以通过多种方式扩展. 例如,Qt Creator架构基于插件加载器,这意味着