p2p技術之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));

 其他待補充


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM