我们已经实现了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进程。