网络广播实验

1,036 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

(实验源码见文章底部)

实验任务
  1. 实现节点广播逻辑 •实现main.c中的broadcast_packet函数

     •instance->iface_list链表中保存所有端口的信息
    
     •收到每个数据包,将该包从所有其它端口发送出去
    

    •结果验证

     •使用three_nodes_bw.py拓扑文件
    
     •三个节点相互能够ping通
    
  2. 验证广播网络传输效率

    •进行iperf测试

     •实验验证广播网络的链路利用效率
    
     •iperf测试(Client -> Server):
    
     	•H1: iperf client; H2, H3: iperf servers,   H1同时给H2,H3发数据
    
     	•H1: iperf server; H2, H3: iperf clients,   H2, H3同时给H1发数据
    
  3. 验证广播网络产生数据环路 •环形网络拓扑

     •三个Hub节点,b1, b2, b3,两两互联
     
     •两个主机节点,h1连接到b1,h2连接到b2
    

    •由h1向h2发送一个数据包

     •h1# ping -c 1 10.0.0.2
     
     •抓包看到一个数据包不断被转发
    
注意事项

1.需要先在集线器节点(b1-b3)上运行hub(或hub-reference),然后在主机节点(h1-h3)上运行相应网络程序(ping或iperf)

2.实验依赖ethtool工具,可用apt进行安装

3.执行iperf测试时,是iperf client给iperf server发送数据

4.网络链路的带宽是双向的

•节点h1与b1之间的链路带宽为20Mbps,h1以20Mbps速率向b1传输数据的同时,b1也能以20Mbps速率向h1传输数据

工具安装

安装mininet时候用naive install时,注意不要./install.sh -a,很容易出错。最好是./install -nfv。另外git clone 如果遇到connection refused情况,需要到install.sh中定位该包,且将git前缀改为https。

git clone https://github.com/mininet/mininet
cd mininet
git tag  # list available versions
git checkout -b mininet-2.3.0 2.3.0  # or whatever version you wish to install
cd ..
mininet/util/install.sh -nfv   #最好是-nfv选项
#检查基本的mininet function是否运行正常
sudo mn

正确情况下结果如下所示:

image.png

广播实验代码/细节分析

TCP Offloading: 将本该由OS负责的数据包处理(TCP分段、IP分片、重组等)的工作放卸载到网卡硬件中去做 It is generally recommended to keep some of them on for client machines because of improved throughput and lower CPU utilization (except LSO), and turn more of them off for servers disable_ipv6: 由于我们已经有了ipv4,如果不禁用ipv6可能在实验模式时会有冲突。

main函数,最后会被编译成可执行hub文件,main中的代码展示了hub的行为逻辑

//hub会持续侦听在它的iface上是否有事件过来,有的话会recvfrom,将发送过来的数据放在buff之中,hub并不会存下数据,而是将buff中的数据memcopy到packet中,再使用broadcast_packet将数据包发送给所有与自己端口连接的主机上。
while (1) {
    int ready = poll(instance->fds, instance->nifs, -1);    //-1表示永远等待侦听
    //pool返回实际发生的事件个数,如果失败,返回-1
        if (ready < 0) {    
            perror("Poll failed!");
            break;
        }
        else if (ready == 0)    //没任何事情发生,结束本次循环,等下次轮询
            continue;
        for (int i = 0; i < instance->nifs; i++) {
	        //hub最重要的操作
            if (instance->fds[i].revents & POLLIN) {
                len = recvfrom(instance->fds[i].fd, buf, ETH_FRAME_LEN, 0, \
                        (struct sockaddr*)&addr, &addr_len);
                if (len <= 0) {
                    log(ERROR, "receive packet error: %s", strerror(errno));
                }
                else if (addr.sll_pkttype == PACKET_OUTGOING) {
                    // XXX: Linux raw socket will capture both incoming and
                    // outgoing packets, while we only care about the incoming 
                       ones.
                    // log(DEBUG, "received packet which is sent from the "
                    //      "interface itself, drop it.");
                }
                else {
                    iface_info_t *iface = fd_to_iface(instance->fds[i].fd);
                    if (!iface)
                        continue;
                    char *packet = malloc(len);
                    if (!packet) {
                        log(ERROR, "malloc failed when receiving packet.");
                        continue;
                    }
                    memcpy(packet, buf, len);   //将接受的数据复制到package中
                    handle_packet(iface, packet, len); //将package广播到其它主机
                }
            }
        }

待定义函数void broadcast_packet 函数接口 (iface_info_t *iface, const char *packet, int len) 函数逻辑:依次检查hub自己的端口,如果不是消息进入时的端口,则将信息转发出去。

void broadcast_packet(iface_info_t *iface, const char *packet, int len) {
    // TODO: broadcast packet
    fprintf(stdout, "TODO: broadcast packet.\n");
    iface_info_t *other_iface = NULL;   //定义其它端口
    list_for_each_entry(other_iface, &instance->iface_list, list) {
        if (other_iface->fd != iface->fd)
            iface_send_packet(other_iface, packet, len);
    }  
}
函数解析:
list_for_each_entry在list.h中定义,它是一个for循环,循环访问iface链表中的所有iface.
#define list_entry(ptr, type, member) \
    (type *)((char *)ptr - offsetof(type, member))
  
#define list_for_each_entry(pos, head, member) \
    for (pos = list_entry((head)->next, typeof(*pos), member); \
            &pos->member != (head); \
            pos = list_entry(pos->member.next, typeof(*pos), member))
iface_send_packet:给定数据和长度,以及发出的转出的端口号便可

//一些数据结构
typedef struct {
    struct list_head list;   //一个双向链表
    int fd;                  //文件描述符
    int index;
    u8  mac[ETH_ALEN];       //MAC地址的长度 6B 
    char name[16];
} iface_info_t;	


typedef struct {
    struct list_head iface_list;    //接口链表
    int nifs;                       //gds中fd的个数
    struct pollfd *fds;             //文件描述符的数组
} ustack_t;    //被实例化为instance了

struct list_head {
    struct list_head *next, *prev;
};    //被实例化为iface_list


struct sockaddr_ll  //表示设备无关的物理层地址结构
{  
unsigned short int sll_family; /* 一般为AF_PACKET */  
unsigned short int sll_protocol; /* 上层协议 */  
int sll_ifindex; /* 接口类型 */  
unsigned short int sll_hatype; /* 报头类型 */  
unsigned char sll_pkttype; /* 包类型 */  
unsigned char sll_halen; /* 地址长度 */  
unsigned char sll_addr[8]; /* MAC地址 */  
};

struct pollfd {  
int fd;        /* 文件描述符 */  
short events; /* 等待的事件 */  
short revents; /* 实际发生了的事件 */  
};



int poll(struct pollfd *fds, nfds_t nfds, int timeout);
//功能:监视并等待多个文件描述符的属性变化

int recvfrom(int s, void *buf, int len, unsigned int flags, [struct] sockaddr *from,int *fromlen);
//接收远程主机经指定的[socket]传来的数据, 并把数据存到由参数buf 指向的内存空间
																	 
实验流程
  1. 测试广播网络之间的连通性
#1. cd到实验代码下,按照上面修改broadcast_packet函数
#2. 直接输入 makeshell自动找Makefile并且默认make all。会编译出可执行hub文件
#3. sudo python3 three_nodes_bw.py会运行mininet并且按照py文件自动建立拓扑网络
#4. mininet中输入 xterm h1 h2 h3 b1打开node终端
#5. 在b1终端./hub运行hub,正确的话会显示检测到的端口
#6. 在其它节点如h1上根据ipping其它节点。所有节点的端口、ip可以在mininet输入dump获得

查找网络的一些基本信息并打开所有节点的终端

image.png 打开后终端显示如下

image.png 在b1上输入./hub,之后再进行连通性测试(可以在用xterm打开h1 h2 h3 单个测试,也可以在mininet上测试)

image.png 发现h1和h2都ping不通h3 解决方法:查看下h1和h2的arp表

image.png 发现对于h3(10.0.0.4)并没有arp地址(mac),我们可以手动添加一个静态地址

image.png 这时h1和h3就可以ping通了,对于h2也是同样加一个HWaddress(要与h1中添加的一致) 现在再测试连通性 h1 ping h2 -c 4

image.png h1 ping h3 -c 4

image.png

h2 ping h1 -c 4

image.png

h2 ping h3 -c 4

image.png

h3 ping h1 -c 4

image.png

h3 ping h2 -c 4

image.png

  1. 测试广播网络的效率 情形1: 将h2和h3作为server端,h1作为客户端,测试带宽利用率

image.png 从上可以看出,带宽利用率为9.34/10*100% = 93.4%。 情形2: h1作为server,h2和h3作为client

image.png 从上可以看出,带宽利用率分别为93.1%和91.8%。

  1. 环形网络拓扑

首先根据实验要求修改three_nodes_bw.py的网络拓扑

image.png

代码如下所示:

image.png

操作步骤:sudo权限打开three_nodes_bw.py文件。然后

mininet> exterm h1 h2 b1 b2 b3

打开终端后在b1、b2、b3中sudo ./hub将主机设置为集线器

如果在h1中ping 10.0.0.2(也就是h2) -c 4,则b1 b2 b3的终端下面会不断地出现brodacast packet的提示。

image.png

说明b1、b2、b3已经形成了一个环路并且环路已经打通。 下面是wireshark捕获的包

image.png

如有错误还望不吝赐教!感谢

Github源码地址: github.com/LongxingHu/…