UCAS-Network-Lab-3: Hub and Switch

代码量最小的一集

本次实验的主体是集线器和交换机,这一次我们工作在数据链路层,需要实现集线器的广播机制和交换机的发送机制。

\text{1. Hub}

在框架代码的基础上实现广播,其实也就是遍历所有可用的端口,然后发送出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void broadcast_packet(iface_info_t *iface, const char *packet, int len)
{
// TODO: broadcast packet
//log(INFO, "Broadcast packet start.");
iface_info_t *iface_info = NULL;
pthread_mutex_lock(&instance->iface_list_lock);
list_for_each_entry(iface_info, &instance->iface_list, list) {
if (iface_info != iface) {
//log(INFO, "Sending packet of length %d to iface %s", len, iface_info->name);
iface_send_packet(iface_info, packet, len);
}
}
pthread_mutex_unlock(&instance->iface_list_lock);
//log(INFO, "Broadcast packet done.");
}

\text{2. Switch}

稍微复杂一点,要实现交换机的学习机制,但其实也很简单。

框架使用哈希表保存了mac地址

首先是最最简单的查哈希表环节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
iface_info_t *lookup_port(u8 mac[ETH_ALEN])
{
// lookup the mac address in mac_port table
// return NULL for non-existing records
// first calc the hash value
log(INFO, "lookup "ETHER_STRING, ETHER_FMT(mac));
int hash = hash8((char *)mac, ETH_ALEN);
log(DEBUG, "hash value of "ETHER_STRING" is %d", ETHER_FMT(mac), hash);
mac_port_entry_t *entry = NULL;
// lock
pthread_mutex_lock(&mac_port_map.lock);
list_for_each_entry(entry, &mac_port_map.hash_table[hash], list) {
if (memcmp(entry->mac, mac, ETH_ALEN) == 0) {
log(DEBUG, "found "ETHER_STRING" in mac_port table", ETHER_FMT(mac));
entry->visited = time(NULL);
// unlock
pthread_mutex_unlock(&mac_port_map.lock);
return entry->iface;
}
}
// unlock
pthread_mutex_unlock(&mac_port_map.lock);
return NULL;
}

注意上锁,因为整个过程中还会有一个线程一边检查一边删除过期的mac地址。同时也要注意更新mac地址的时间戳。

接下来是插入,其实也不困难,所有的操作说到底都是哈希表的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void insert_mac_port(u8 mac[ETH_ALEN], iface_info_t *iface)
{
// insert mac -> iface pair into mac_port table
// it is just a insertion to a hash table
// first calc the hash value
log(INFO, "insert "ETHER_STRING" -> %s into mac_port table", ETHER_FMT(mac), iface->name);
int hash = hash8((char *)mac, ETH_ALEN);
log(DEBUG, "hash value of "ETHER_STRING" is %d", ETHER_FMT(mac), hash);
// lock
pthread_mutex_lock(&mac_port_map.lock);
mac_port_entry_t *entry = (mac_port_entry_t *)malloc(sizeof(mac_port_entry_t));
memcpy(entry->mac, mac, ETH_ALEN);
entry->iface = iface;
entry->visited = time(NULL);
list_add_tail(&entry->list, &mac_port_map.hash_table[hash]);
pthread_mutex_unlock(&mac_port_map.lock);
}

同样地注意线程安全和时间戳更新。

然后是删除过期的mac地址,下面的这个函数每秒钟被调用一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sweep_aged_mac_port_entry()
{
// lock
pthread_mutex_lock(&mac_port_map.lock);
mac_port_entry_t *entry, *q;
time_t now = time(NULL);
int n = 0;
for (int i = 0; i < HASH_8BITS; i++) {
list_for_each_entry_safe(entry, q, &mac_port_map.hash_table[i], list) {
if (now - entry->visited > MAC_PORT_TIMEOUT) {
list_delete_entry(&entry->list);
free(entry);
n++;
}
}
}
// unlock
pthread_mutex_unlock(&mac_port_map.lock);
return 0;
}

最后是交换机的发送机制,在这个过程中我们需要进行交换机的学习。如果能够查找到目标地址,那么就单播,否则就广播。

同时,如果能在哈希表中找到发送方的地址,那么就更新时间戳,否则就插入,这样我们就知道发送方的mac地址对应于哪个端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void handle_packet(iface_info_t *iface, char *packet, int len)
{
struct ether_header *eh = (struct ether_header *)packet;
log(DEBUG, "the dst mac address is " ETHER_STRING ".\n", ETHER_FMT(eh->ether_dhost));
log(DEBUG, "the src mac address is " ETHER_STRING ".\n", ETHER_FMT(eh->ether_shost));
// if the dest mac address is found in mac_port table, forward it; otherwise,
// broadcast it.
if (is_broadcast(eh->ether_dhost)) {
log(DEBUG, "the dst mac address is broadcast address, broadcast it.");
broadcast_packet(iface, packet, len);
free(packet);
return;
}
iface_info_t *dst_iface = lookup_port(eh->ether_dhost);
if (dst_iface != NULL) {
log(DEBUG, "found " ETHER_STRING " in mac_port table, forward it.", ETHER_FMT(eh->ether_dhost));
iface_send_packet(dst_iface, packet, len);
}
else {
log(DEBUG, "not found " ETHER_STRING " in mac_port table, broadcast it.", ETHER_FMT(eh->ether_dhost));
broadcast_packet(iface, packet, len);
}
if (!lookup_port(eh->ether_shost))
insert_mac_port(eh->ether_shost, iface);
free(packet);
}