简单覆盖网络(SON)

我们已经实现了DartNet的SRT协议。之所以设计覆盖层,因为我们不能在互联网的真实路由器上运行我们的简单网络协议(SNP)。这是一个非常酷的方法来绕过不能更改路由器代码的限制。带上你的好奇心,开始了解它吧。在本篇文章,我们将讨论覆盖网络层的设计

本篇文章的具体内容:

  • 覆盖层拓扑和节点ID
  • 覆盖层的构造
  • 邻居表
  • SNP数据包格式
  • 覆盖过程实现
  • 测试叠加

覆盖层拓扑和节点ID

DartNet建立在覆盖层网络上。这个覆盖层网络的拓扑在topology.dat文件中定义。topology.dat中的每一行具有以下格式:
host1 host2 link cost

其中host1是计算机的主机名,host2是另一台计算机的主机名,link cost是这两个主机之间的直接链接开销。

覆盖拓扑

在DartNet中,使用nodeID来标识主机。nodeID与TCP/IP中的IP地址具有类似 的作用。nodeID是由主机IP地址的后8位表示的整数。例如,IP地址为“202.120.95.36”的主机作为节点ID 36。

覆盖层的构造

我们的覆盖层网络中有4个节点。在每个节点上,有一个ON进程和SNP进程在运行。ON进程和SNP进程与本地TCP连接相连。ON过程还保持与所有相邻节点的TCP连接。例如,左上节点在覆盖网络中具有3个相邻节点。存在到每个相邻节点的TCP连接。

覆盖过程图

节点到邻居的每个TCP连接,都有一个listen_to_neighbor线程。listen_to_neighbor线程保持从该邻居接收分组并且将接收的分组转发到SNP进程。对于ON进程和SNP进程之间的TCP连接,主线程继续从SNP进程接收包含分组和下一跳的nodeID的sendpkt_arg_t结构,并将这些分组发送到下一跳节点

左上节点的ON线程图

邻居表

ON进程维护邻居表。邻居表包含相邻节点的信息。邻居表在overlay / neighbortable.h中定义

//neighbortable entry definition
//a neighbor table contains n entries where n is the number of neighbors

typedef struct neighborentry {
  int nodeID;       //neighbor’s node ID
  in_addr_t nodeIP; //neighbor’s IP address
  int conn;         //TCP connection’s socket descriptor to the neighbor
} nbr_entry_t;

邻居表具有n个entry,其中n是覆盖层中的邻居的数目。邻居表中的每个entry包含邻居的nodeID,邻居的IP地址和TCP连接对邻居的套接字描述符

SNP数据包格式

ON过程从覆盖层网络接收SNP分组并转发到SNP层。ON过程还从SNP层接收SNP分组,并转发到覆盖层网络。SNP包格式在common / pkt.h中定义

数据包格式图

//packet type definition, used for type field in the SNP header
#define ROUTE_UPDATE 1
#define SNP 2

//SNP packet format definition
typedef struct snpheader {
  int src_nodeID;               //source node ID
  int dest_nodeID;              //destination node ID
  unsigned short int length;    //length of the data in the packet
  unsigned short int type;      //type of the packet
} snp_hdr_t;

typedef struct packet {
  snp_hdr_t header;
  char data[MAX_PKT_LEN];
} snp_pkt_t;

如果分组类型等于SNP,则分组中包含的数据将是一个段(包括段头和数据)。如果分组的类型是ROUTE_UPDATE,则节点的距离向量包含在分组的数据字段中。

//A route update entry

typedef struct routeupdate_entry {
  unsigned int nodeID;  // destination nodeID
  unsigned int cost; // link cost between source (src_nodeID in header) and destination nodes
} routeupdate_entry_t;

//route update packet format

typedef struct pktrt{
  unsigned int entryNum;  // number of entries contained and in this route update packet
  routeupdate_entry_t entry[MAX_NODE_NUM];
} pkt_routeupdate_t;

路由更新数据包格式

覆盖层网络API

通过TCP连接发送数据时,我们使用分隔符。使用“!&”表示数据传输的开始,使用“!#”表示数据传输的结束。因此,当数据通过TCP连接发送时,首先发送“!&”,然后发送数据,然后发送“!#”。接收方只需相应地根据分割符进行解析接受即可

API的说明

ON进程为SNP进程提供两个函数调用:overlay_sendpkt()和overlay_recvpkt()。

typedef struct sendpktargument {
  int nextNodeID;    //node ID of the next hop
  pkt_t pkt;         //the packet to be sent
} sendpkt_arg_t

int overlay_sendpkt(int nextNodeID, pkt_t* pkt, int overlay_conn)
int overlay_recvpkt(pkt_t* pkt, int overlay_conn)

ON进程使用getpktToSend()获取一个sendpkt_arg_t数据结构,该结构包含一个数据包和来自SNP进程的下一跳的nodeID。ON进程使用forwardpktToSNP()将数据包转发到SNP进程。

int getpktToSend(pkt_t* pkt, int* nextNode,int network_conn);
int forwardpktToSNP(pkt_t* pkt, int network_conn);

ON进程使用sendpkt()向邻居发送数据包,并使用recvpkt()从邻居接收数据包。

int sendpkt(pkt_t* pkt, int conn);
int recvpkt(pkt_t* pkt, int conn);

覆盖过程实现

ON过程的代码如下

//start overlay initialization
  printf("Overlay: Node %d initializing...\n", topology_getMyNodeID());

  //create a neighbor table
  nt = nt_create();

  //initialize network_conn to -1, means no network layer process is connected yet
  network_conn = -1;

  //register a signal handler which is used to terminate the process
  signal(SIGINT, overlay_stop);

  //print out all the neighbors
  int nbrNum = topology_getNbrNum();
  int i;
  for(i=0;i<nbrNum;i++) {
    printf("Overlay: neighbor %d:%d\n",i+1,nt[i].nodeID);
  }

  //start the waitNbrs thread to wait for incoming connections
  //from neighbors with larger node IDs

  pthread_t waitNbrs_thread;
  pthread_create(&waitNbrs_thread,NULL,waitNbrs,(void*)0);

  //wait for neighboring nodes to start the overlay process

  sleep(OVERLAY_START_DELAY);

  //connect to neighbors with smaller node IDs

  connectNbrs();

  //wait for waitNbrs thread to return

  pthread_join(waitNbrs_thread,NULL);

  //at this point, all connections to the neighbors are created

  //create threads listening to all the neighbors

  for(i=0;i<nbrNum;i++) {
    int* idx = (int*)malloc(sizeof(int));
    *idx = i;
    pthread_t nbr_listen_thread;
    pthread_create(&nbr_listen_thread,NULL,listen_to_neighbor,(void*)idx);
  }

  printf("Overlay: node initialized...\n");
  printf("Overlay: waiting for connection from SNP process...\n");

  //waiting for connection from  SNP process

  waitNetwork();

当ON进程启动时,创建并初始化邻居表。然后注册信号处理器overlay_stop()用于信号SIGINT,当接收到SIGINT信号时,调用overlay_stop()以终止ON过程。

建立到覆盖网络中的所有邻居的TCP连接。在我们的设计中,如果两个节点之间存在链接,则具有较小节点ID的节点将在CONNECTION_PORT上打开一个TCP端口,具有较大节点ID的节点将连接到该节点

ON进程启动waitNbrs线程,然后一段时间后调用connectNbrs()函数。waitNbrs线程在CONNECTION_PORT上打开一个TCP端口,并等待来自节点ID比自身节点ID大的所有邻居的传入连接。待所有传入连接建立后,waitNbrs线程返回。

对于与邻居的每个TCP连接,ON过程启动listen_to_neighbor线程。每个listen_to_neighbor线程使用recvpkt()保持从邻居接收数据包,并使用forwardpktToSNP()将数据包转发到SNP进程。

当所有的listen_to_neighbor线程都启动,ON进程调用waitNetwork()函数。此函数在OVERLAY_PORT上打开一个TCP端口,并等待来自本地SNP进程TCP连接。建立连接后,waitNetwork()函数继续从SNP进程接收sendpkt_arg_t结构。之后waitNetwork()使用sendpkt()将数据包发送到覆盖层网络中的下一跳。当SNP进程断开时,waitNetwork()关闭连接并等待来自本地SNP进程的下一个连接

ON进程在接收到SIGINT信号时终止。关闭所有TCP连接,释放所有动态分配的内存并终止ON过程来停止覆盖

测试叠加层

SNP进程连接到本地ON进程,并且请求本地过程周期性地广播路由更新分组。SNP过程应该能够从其邻居接收路由更新分组。通过发送和接收路由更新分组,我们可以验证覆盖是否正常工作。

printf("network layer is starting, pls wait...\n");

  //initialize global variables

  overlay_conn = -1;

  //register a signal handler which will kill the process

  signal(SIGINT, network_stop);

  //connect to overlay

  overlay_conn = connectToOverlay();
  if(overlay_conn<0) {
    printf("can’t connect to ON process\n");
    exit(1);
  }

  //start a thread that handles incoming packets from overlay

  pthread_t pkt_handler_thread;
  pthread_create(&pkt_handler_thread,NULL,pkthandler,(void*)0);

  //start a route update thread

  pthread_t routeupdate_thread;
  pthread_create(&routeupdate_thread,NULL,routeupdate_daemon,(void*)0);

  printf("network layer is started...\n");

  //sleep forever
  while(1) {
    sleep(60);
  }

在ON进程中调用waitNetwork()后,SNP进程启动。当SNP过程开始时,SNP过程首先通过将与本地ON过程的连接设置为-1作为无效。

然后SNP进程调用connecToOverlay()函数连接到端口OVERLAY_PORT上的ON进程。在建立与ON进程的TCP连接之后,SNP进程启动pkthandler线程处理来自ON进程的传入数据包,启动routeupdate_daemon线程广播路由更新。目前,广播路由更新报文。广播通过将SNP分组报头中的dest_nodeID设置为BROADCAST_NODEID来完成,并使用overlay_sendpkt()将路由更新分组发送到广播地址BROADCAST_NODEID

最后SNP进程进入while循环,并永久休眠。当接收到SIGINT信号时终止SNP进程。