【计网实验——prj5】交换机转发实验
实验要求
1. 实现对数据结构mac_port_map的所有操作,以及数据包的转发和广播操作,具体包括以下函数:
iface_info_t *lookup_port(u8 mac[ETH_ALEN]);
void insert_mac_port(u8 mac[ETH_ALEN], iface_info_t *iface);
int sweep_aged_mac_port_entry();
void broadcast_packet(iface_info_t *iface, const char *packet, int len);
void handle_packet(iface_info_t *iface, char *packet, int len);
2. 使用iperf和给定的拓扑结构进行实验,对比交换机转发与集线器广播的性能。
实现方案
转发表学习实现
1. 查询操作
每收到一个数据包,根据目的MAC地址查询相应转发条目,如果查询到对应条目,则根据相应转发端口转发数据包;否则,广播该数据包。这个过程通过函数iface_info_t *lookup_port(u8 mac[ETH_ALEN]);
实现,由于需要访问的转发表mac_port_map
属于临界资源,即可能存在另一个线程访问转发表进行其他操作,因此需要加上锁来确保操作的原⼦性。
// lookup the mac address in mac_port table
iface_info_t *lookup_port(u8 mac[ETH_ALEN])
{
// TODO: implement the lookup process here
fprintf(stdout, "TODO: implement the lookup process here.\n");
mac_port_entry_t *entry = NULL;
pthread_mutex_lock(&mac_port_map.lock);
for (int i = 0; i < HASH_8BITS; i++) {
list_for_each_entry(entry, &mac_port_map.hash_table[i], list) {
fprintf(stdout, "Comparing MAC...\n");
int i = 0;
while(entry->mac[i] == mac[i] && i < ETH_ALEN){
fprintf(stdout, "%d\n", i);
i++;
}
if(i == ETH_ALEN){
//entry->visited = time(NULL);
pthread_mutex_unlock(&mac_port_map.lock);
fprintf(stdout, "Found related port.\n");
return entry->iface;
}
}
}
pthread_mutex_unlock(&mac_port_map.lock);
return NULL;
}
2. 插入操作
每收到一个数据包,如果其源MAC地址-入端口映射关系在转发表中,更新访问时间;否则,将该地址与入端口的映射关系写入转发表。这个过程通过函数void insert_mac_port(u8 mac[ETH_ALEN], iface_info_t *iface);
实现,该操作同样需要保证原子性。
// insert the mac -> iface mapping into mac_port table
void insert_mac_port(u8 mac[ETH_ALEN], iface_info_t *iface)
{
// TODO: implement the insertion process here
fprintf(stdout, "TODO: implement the insertion process here.\n");
pthread_mutex_lock(&mac_port_map.lock);
mac_port_entry_t *new_port_entry = init_port_entry(mac, iface);
u8 hash_value = hash8((char *)mac, ETH_ALEN);
list_add_tail(&new_port_entry->list, &mac_port_map.hash_table[hash_value]);
pthread_mutex_unlock(&mac_port_map.lock);
}
3. 老化操作
每秒钟运行一次老化操作,删除超过30秒未访问的转发条目。这个过程由函数int sweep_aged_mac_port_entry();
实现,该操作同样需要保证原子性。
// sweeping mac_port table, remove the entry which has not been visited in the
// last 30 seconds.
int sweep_aged_mac_port_entry()
{
// TODO: implement the sweeping process here
//fprintf(stdout, "TODO: implement the sweeping process here.\n");
mac_port_entry_t *entry = NULL;
mac_port_entry_t *q = NULL;
time_t now = time(NULL);
int rm_entry_num = 0;
pthread_mutex_lock(&mac_port_map.lock);
for (int i = 0; i < HASH_8BITS; i++) {
list_for_each_entry_safe(entry, q, &mac_port_map.hash_table[i], list) {
if((int)(now - entry->visited) > MAC_PORT_TIMEOUT){
list_delete_entry(&entry->list);
free(entry);
rm_entry_num ++;
}
}
}
pthread_mutex_unlock(&mac_port_map.lock);
return rm_entry_num;
}
数据包处理
收到数据包后,交换机根据转发表中对应的转发端口转出数据包,若没有在转发表中查询到对应端口,则直接广播该数据包。这个过程由函数void handle_packet(iface_info_t *iface, char *packet, int len);
实现,其中广播功能沿用上一次实验实现的void broadcast_packet(iface_info_t *iface, const char *packet, int len);
函数。
// handle packet
// 1. if the dest mac address is found in mac_port table, forward it; otherwise,
// broadcast it.
// 2. put the src mac -> iface mapping into mac hash table.
void handle_packet(iface_info_t *iface, char *packet, int len)
{
// TODO: implement the packet forwarding process here
fprintf(stdout, "TODO: implement the packet forwarding process here.\n");
struct ether_header *eh = (struct ether_header *)packet;
iface_info_t *dest_iface = lookup_port(eh->ether_dhost);
if(dest_iface != NULL){
iface_send_packet(dest_iface, packet, len);
}
else{
broadcast_packet(iface, packet, len);
}
if(lookup_port(eh->ether_shost) == NULL){
insert_mac_port(eh->ether_shost, iface);
}
log(DEBUG, "the dst mac address is " ETHER_STRING ".\n", ETHER_FMT(eh->ether_dhost));
free(packet);
}
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);
}
}
运行结果
利用给出的拓扑结构进行 iperf 测试,本设计的测试结果如下:
上图中 h1 节点同时接收 h2 节点和 h3 节点,可以看出 h2 节点和 h3 节点的发送带宽分别为 8.64Mbps 和 9.16Mbps ,利用率为 89% 。
switch-reference 的结果如下:
上图中 h1 节点同时接收 h2 节点和 h3 节点,可以看出 h2 节点和 h3 节点的发送带宽分别为 9.33Mbps 和 9.22Mbps ,利用率为 92.75% ,与本设计的结果接近。
而 hub-reference 的结果如下:
上图中 h1 节点同时接收 h2 节点和 h3 节点,可以看出 h2 节点和 h3 节点的发送带宽分别为 2.49Mbps 和 6.78Mbps ,利用率为 46.35% 。
在本实验中, switch 的带宽利用率是 hub 的两倍 。因此 switch 利用转发表的⽅式明显比 hub 的直接⼴播模式效率要高。
思考题
1. 交换机在转发数据包时有两个查表操作:根据源MAC地址、根据目的MAC地址,为什么在查询源MAC地址时更新老化时间,而查询目的MAC地址时不更新呢?
假设在查询目的MAC地址时和查询源MAC地址时都更新老化时间,那么考虑一种特殊情况,在给某个主机发送数据包的过程中,主机地址发生了改变(如从交换机的一个端口换到了另一个端口),此时其原地址已经被交换机接收并且开始查询是否存在与该地址相关的条目,查找到之后对该条目的访问时间进行更新,并且将数据包发送给该地址。此时由于主机已经更换了地址,主机收不到数据包,因此发送方也不会收到接收成功的信息,那么发送方会重复发送操作,往相同的地址持续发送数据包(还是原地址),直到收到接收成功的信息为止。而每一次发送,都需要进行目的MAC地址的查表操作,其访问时间也会持续更新,这就会导致原来的错误地址相关的条目会一直留在转发表中,发送方也只会不停地给错误地址对应的端口发送数据包,进入死循环,无限消耗资源和效率。所以在查询目的MAC地址时不能更新老化时间。
如果只在查询源MAC地址时进行老化时间的更新,在主机切换端口后,其原地址由于已经废弃不会再发送数据包,老化时间也不再更新,那么错误地址很快就会从转发表中删除,其新的地址与端口信息也会在主机发送一次数据包之后重新学习到转发表中,最大程度上避免交换机转发的决策错误,提高网络传输的效率。
2. 网络中存在广播包,即发往网内所有主机的数据包,其目的MAC地址设置为全0xFF ,例如ARP请求数据包。这种广播包对交换机转发表逻辑有什么影响?
一定的广播有利于交换机转发表的学习。设想在整个传输网络刚刚启动时,转发表的内容为空,主机A想与主机B进行通信,会经过如下过程:
- A 检查自己的 ARP 表,发现 B 的 MAC 地址不在自己的 ARP 表里(注意:ARP 表里记录了 IP 地址和 MAC 地址之间的对应关系,因而需要首先检查 ARP 表,通过目的 IP 地址得到 MAC 地址,再进行数据报文的发送操作);
- 因此 A 只能向交换机发出 ARP 请求数据包(该数据包为目的MAC地址全为 0xFF 的广播包);
- 交换机学习 A 的 MAC 地址到自己的转发表,并广播 ARP 请求报文;
- B 接收到 ARP 请求报文,学习 A 的 MAC 地址到自己的 ARP 表;
- B 向交换机发出 ARP 回应报文(当其他主机接收这个⼴播包之后,不会接受它);
- 交换机学习 B 的 MAC 地址到自己的转发表,并向 A 发出 ARP 回应报文;
- A 接受到 B 的 ARP 回应报文,并学习 B 的 MAC 的地址;
此时 A 与 B 就建立起了通信联系,交换机在这个过程中完成了两个(一对)转发条目的学习。
3. 理论上,足够多个交换机可以连接起全世界所有的终端。请问,使用这种方式连接亿万台主机是否技术可行?并说明理由。
不可行。仅用交换机连接亿万台主机形成的网络结构较为单一,功能上存在很多限制,且这样缺少层次化的结构不利于维护。连接数量庞大的主机需要巨大的转发表作为支撑,如果转发表空间有限,而网络出现大量使用时,可能会导致转发表中的映射项不断地被更新替换出来,此时交换机网络和 hub 网络可能会效率⼀样的低。即使内存足够,在庞大的转发表上进行查询和老化等操作所需要花费的时间也是大量的,仍然会导致效率低下。再者,如果遇到需要广播的情况,需要对整个网络中所有的主机进行数据包传输,在主机过多时可能会导致通信拥挤和安全漏洞。最后,交换机仅根据转发表学习机制来进行转发决策,决策方式单一,如果增加网络层次结构,可以获取更多协议信息,进行更加智能的转发决策。倘若全部用交换机进行连接而不采取其他协议,还会遇到数据安全的问题,容易收到各种各样的网络攻击。