p2p技術之n2n源碼核心簡單分析一

首先在開篇以前介紹下內網打洞原理node

場景:一個服務器S1在公網上有一個IP,兩個私網機器C1,C2linux

C1,C2分別由NAT1和NAT2鏈接到公網,咱們須要藉助S1將C1,C2創建直接的TCP鏈接,即由C1向C2打一個洞,讓C2能夠沿這個洞直接鏈接到C1主機,也就成了局域網訪問的模式。git

實現過程以下:github

  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外網訪問的部署。同時基於代碼量很小,實現很巧妙,對其源碼進行了初步閱讀。bash

對幾個核心點進行記錄服務器

文件功能

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

 其餘待補充併發

相關文章
相關標籤/搜索