# 实验七

## 写在前面

这个实验应该是实验文档指导最模糊，最需要个人对网络层寻路逻辑，邻居更新等的深刻理解的一次实验。

实验看似只需要实现六个函数，实际上这六个线程函数需要引申调用更多的函数（并且没有指导），最后实际上我自己补了大概20个函数不止才使得这个逻辑比较清晰。

最后的实现大概用了1200行左右，如果去掉log大概1000行。

同时debug也是一个问题，这里我给出一个基础的判断标准：

在你的本地用topo.py,在四个路由器上执行./mospfs,不出现seg fault, double free等导致程序crash的行为，大概40s后如果你h1 ping h2能ping通，就可以去oj先测试一下了（具体还有更多的细节测试方向，后面说）

但是你本地能ping通不完全代表能过oj，只是说大概率。

感觉抓包没啥用，不如打log。

那个reference可以自己跑一下抓包理解一下mospf是怎么执行的，但我感觉没啥用。（有用的是一个骚操作，后面说，有点禁忌）

以及你贴过去的实验五的文件，如果你实验五能每次都100%概率pass,就完全不需要（也不应该）怀疑什么seg fault, ip header参数设置错误（如 protocol=0x0000）,double free是你实验五的文件的问题，那是在浪费你的时间。

但是如果你实验五不是百分百能过，那你还得改实验五，所以这个实验就很难。

{% hint style="success" %}
由于这个实验的文档指导太模糊了，本人接下来将仿照实验三原实验文档的写法，提供每个我的函数的实现注释指导和部分实用代码，并在最后给出经验和提示。（这将是最详细的一次补充文档）
{% endhint %}

***

## 我们开始吧：先理解mOSPF

实验五和七的区别在哪里？实验七多了mOSPF。为什么说是多了？因为实验五的拓扑（topo.py）对你的网络的节点和路由器是硬link的，而实验七的topo.py没有做这个。因此，实验七的mOSPF充当了寻找neighbor和拓扑路径的工作。mOSPF做了简化，只有hello和lsu报文，简化理解来说，hello报文用来宣称我在这里，lsu报文用来宣称我知道有哪些路由器，哪些路径，广而告之。

很显然，dijikstra算法需要知道全局的图信息，所以mOSPF稳定后，每个路由器应该知道拓扑结构的全貌。

那么这四个要实现的函数是什么呢？是四个线程。请注意到Main函数调用的mospf\_run():

```c
void mospf_run()
{
	pthread_t hello, lsu, nbr, db;
	pthread_create(&hello, NULL, sending_mospf_hello_thread, NULL);
	pthread_create(&lsu, NULL, sending_mospf_lsu_thread, NULL);
	pthread_create(&nbr, NULL, checking_nbr_thread, NULL);
	pthread_create(&db, NULL, checking_database_thread, NULL);
}
```

这下懂了吧，就是四个独立的线程（但是有数据冲突，需要加锁，一个锁就行）

sending\_mospf\_hello\_thread每1s向周围广播hello报文，宣告这个节点的存在；

sending\_mospf\_lsu\_thread周期性地向周围广播LSU报文，让这个节点知道的拓扑结构广而告之；

checking\_nbr\_thread周期性检查邻居是否还活着，及时清理和修改拓扑路径；

checking\_database\_thread周期性检查数据库中的路径是否死了，及时清空条目；

这四个线程和另外要实现的两个函数相辅相成：

```c
void handle_mospf_packet(iface_info_t *iface, char *packet, int len)
{
	struct iphdr *ip = (struct iphdr *)(packet + ETHER_HDR_SIZE);
	struct mospf_hdr *mospf = (struct mospf_hdr *)((char *)ip + IP_HDR_SIZE(ip));

	if (mospf->version != MOSPF_VERSION) {
		log(ERROR, "received mospf packet with incorrect version (%d)", mospf->version);
		return ;
	}
	if (mospf->checksum != mospf_checksum(mospf)) {
		log(ERROR, "received mospf packet with incorrect checksum");
		return ;
	}
	if (ntohl(mospf->aid) != instance->area_id) {
		log(ERROR, "received mospf packet with incorrect area id");
		return ;
	}

	switch (mospf->type) {
		case MOSPF_TYPE_HELLO:
            log(DEBUG, "Received mOSPF Hello packet\n");
			handle_mospf_hello(iface, packet, len);
			break;
		case MOSPF_TYPE_LSU:
            log(DEBUG,"Received mOSPF LSU packet\n");
			handle_mospf_lsu(iface, packet, len);
			break;
		default:
			log(ERROR, "received mospf packet with unknown type (%d).", mospf->type);
			break;
	}
}
```

在`ustack_run`调用后，节点接收到包后会调用`handle_packet`并进入`handle_ip_packet`。**此处的实现与之前的实现不同**。具体地，代码中加入了对上层协议的判断，若协议号为`IPPROTO_MOSPF`，则会进入`handle_mospf_packet`处理流程。特别地，对于组播地址`MOSPF_ALLSPFRouters`，也会进入该处理流程。

但是这对你的原来的实验五实现不会造成影响。如果你想检查你的实验五有没有问题，可以这么做：

{% hint style="info" %}
1.注释掉下面这个函数的mOSPF部分；(下面是示例注释方式)

2.注释掉mospf\_run里的四个thread;

3.执行topo.py,如果不会seg fault或者其他crash，说明你的实验五实现应该没问题。
{% endhint %}

```c
void handle_ip_packet(iface_info_t *iface, char *packet, int len)
{
	struct iphdr *ip = packet_to_ip_hdr(packet);
	u32 daddr = ntohl(ip->daddr);
	if (daddr == iface->ip) {
		if (ip->protocol == IPPROTO_ICMP) {
			struct icmphdr *icmp = (struct icmphdr *)IP_DATA(ip);
			if (icmp->type == ICMP_ECHOREQUEST) {
				icmp_send_packet(packet, len, ICMP_ECHOREPLY, 0);
			}
		}
		else if (ip->protocol == IPPROTO_MOSPF) {
			//handle_mospf_packet(iface, packet, len);
		}

		free(packet);
	}
	else if (ip->daddr == htonl(MOSPF_ALLSPFRouters)) {
		//assert(ip->protocol == IPPROTO_MOSPF);
		//handle_mospf_packet(iface, packet, len);

		free(packet);
	}
	else {
		ip_forward_packet(daddr, packet, len);
	}
}
```

会crash的主要原因就是你的free packet在实验五写的太随便，不要改handle\_ip\_packet的free(packet),尝试修改你其他的实验五函数里的free(packet)的错误。

回到两个你要实现的函数handle\_mospf\_hello和handle\_mospf\_lsu，这俩就是收到hello和lsu报文的处理函数，收到hello，说明邻居活着，记录一下，收到lsu，说明路径有更新，记录一下。

***

## 来吧：实现函数

让我们由易到难实现。

### 1.send hello报文区

```c
//每1s发送一次，加锁，后面所有的锁都是这个锁（记得初始化锁），不再赘述
//使用list_for_each_entry对instance->iface_list进行遍历，对其helloint--,
//然后检查邻居链表的helloint是否减到了0，如果是，那么发送hello报文，
//将其helloint重置为MOSPF_DEFAULT_HELLOINT
//这个线程不应该退出
//调用自写函数：send_mospf_hello
//你需要使用的:iface_info_t,list_for_each_entry，MOSPF_DEFAULT_HELLOINT
extern ustack_t *instance;
pthread_mutex_t mospf_lock;
void *sending_mospf_hello_thread(void *param)
{
	//fprintf(stdout, "TODO: send mOSPF Hello message periodically.\n");
	  while(1){
		sleep(1);
		pthread_mutex_lock(&mospf_lock);
       // log(DEBUG,"send mospf hello packet periodically.\n");
		//...
		pthread_mutex_unlock(&mospf_lock);
	  }
	 return NULL;
}
```

```c
//发送hello报文函数
//1.计算报文总长度（ETHER+IPHEADER+MOSPFHEADER+HELLO）,均有对应的宏。
//2.构造packet(不同头部都有对应的现成的struct):构造以太网头部，注意是广播地址；构造IP头部，MOSPF头部，HELLO消息，除了以太网头部均有框架现成的函数。
//3.计算并设置校验和，最后做
//4.使用iface_send_packet发送数据包
//需要的函数：ip_init_hdr,mospf_init_hdr,mospf_init_hello,mospf_checksum
void send_mospf_hello(iface_info_t *iface){
//    log(DEBUG,"send mospf hello packet in function\n");
	// 计算报文总长度
    int len =//...;
    char *packet = malloc(len);
    // 构造以太网头部
    //...
    // 构造IP头部
    //...
    // 构造MOSPF头部
    struct mospf_hdr *mospf = (struct mospf_hdr *)(packet + ETHER_HDR_SIZE + IP_BASE_HDR_SIZE);
    //...
    // 构造Hello消息
    //...
    // 计算并设置校验和
    mospf->checksum = mospf_checksum(mospf);
    // 发送数据包
    iface_send_packet(iface, packet, len);

	return;
}
```

### 2.send\_lsu区

```c
//根据自己节点的lsuint定时发送lsu报文，更新序列号和计时器，加锁即可。
//过于简单，直接贴全
//调用自加函数：send_mospf_lsu
void *sending_mospf_lsu_thread(void *param)
{
	//fprintf(stdout, "TODO: send mOSPF LSU message periodically.\n");
   
	while(1){
	sleep(1);
	pthread_mutex_lock(&mospf_lock);
   // log(DEBUG,"send mospf lsu packet periodically.\n");
	instance->lsuint--;
	if (instance->lsuint <= 0) {
		// 发送LSU报文
		send_mospf_lsu();
		// 更新序列号
		instance->sequence_num++;
		// 重置计时器
		instance->lsuint = MOSPF_DEFAULT_LSUINT;
	}
	pthread_mutex_unlock(&mospf_lock);
}
	return NULL;
}
```

```c
// 发送MOSPF LSU消息
//1.调用自加函数generate_mospf_lsu,生成lsu消息
//2.使用双层list_for_each_entry,遍历instance->iface_list中的nbr_list（即所有接口）发送LSU
//发送LSU同样需要先构造数据包的其他header(也就是报头的层层加)，请自行思考
//ip报头可以使用packet_to_ip_hdr和 ip_init_hdr函数
//最后发报文用ip_send_packet函数
//所有报文发完后，释放LSU消息缓冲区
void send_mospf_lsu(void)
{
    // 生成LSU消息
    int mospf_len;
    char *mospf_msg = generate_mospf_lsu(&mospf_len);
    if (!mospf_msg) {
       // log(ERROR, "Failed to generate LSU message");
        return;
    }
    // 遍历所有接口发送LSU
    iface_info_t *iface;
    list_for_each_entry(iface, &instance->iface_list, list) {
        // 遍历该接口的所有邻居
        mospf_nbr_t *nbr;
        list_for_each_entry(nbr, &iface->nbr_list, list) {
            // 计算数据包总长度
            //...
            // 初始化以太网头部
            //...
          // 设置目的MAC为广播地址,设置shost
            // 复制MOSPF消息到IP负载部分
            //...
            // 初始化IP头
            //...

            // 发送数据包
            ip_send_packet(packet, pkt_len);

           // log(DEBUG, "Sent LSU to neighbor %x via interface %s", 
              //  nbr->nbr_ip, iface->name);
        }
    }

    // 释放LSU消息缓冲区
    free(mospf_msg);
}
```

```c
// 生成MOSPF LSU消息的辅助函数
//1.根据instance->iface_list,计算所有邻居数量（没有邻居的接口也算一个）,然后根据该信息计算消息总长度,分配内存
//2.初始化MOSPF头部，LSU头部，可用mospf_init_hdr和mospf_init_lsu函数
//3.填充LSA，对没有邻居的接口,添加一个空条目，否则遍历该接口的所有邻居进行填充
//4.计算校验和，返回消息
char *generate_mospf_lsu(int *len)
{
    // 计算所有邻居数量
    //log(DEBUG, "Generating mOSPF LSU message\n");
    //...
    // 计算消息总长度
    //...
    
    // 分配内存
    char *buffer = malloc(*len);
    struct mospf_hdr *mospf = (struct mospf_hdr *)buffer;
    struct mospf_lsu *lsu = (struct mospf_lsu *)((char *)mospf + MOSPF_HDR_SIZE);
    struct mospf_lsa *lsa = (struct mospf_lsa *)((char *)lsu + MOSPF_LSU_SIZE);

    // 初始化MOSPF头部
    //...
    // 初始化LSU头部
    //...

    // 填充LSA条目
    list_for_each_entry(iface, &instance->iface_list, list) {
        if (iface->num_nbr == 0) {
            // 没有邻居的接口,添加一个空条目
            //...
        } else {
            // 遍历该接口的所有邻居
            //...
            }
        }
    }
    // 计算校验和
    mospf->checksum = mospf_checksum(mospf);

    return buffer;
}
```

### 3.检查邻居似没似区

```c
//创建临时链表存储要删除的邻居（为了避免死锁，实验五已说过），然后上锁
//遍历每个接口的所有邻居，如果邻居已经超时（nbr->alive==0），从接口邻居列表中移除，添加到待删除列表，更新邻居数量；
//否则，递减alive计数器
//解锁后，如果有需要删除的邻居，释放所有待删除的邻居节点，由于邻居关系发生变化，需要更新序列号并发送新的LSU（调用自己写的send_mospf_lsu函数）
void *checking_nbr_thread(void *param)
{
    while (1) {
        sleep(1);
        
        // 创建临时链表存储要删除的邻居
        //...
        
        pthread_mutex_lock(&mospf_lock);
        
        // 遍历所有接口
        iface_info_t *iface;
        list_for_each_entry(iface, &instance->iface_list, list) {
            // 遍历每个接口的所有邻居
            mospf_nbr_t *nbr, *q;
            list_for_each_entry_safe(nbr, q, &iface->nbr_list, list) {
                // 如果邻居已经超时, 从接口邻居列表中移除,添加到待删除列表,更新邻居数量
                    log(DEBUG,"neighbor timeout, remove it.\n");
                    
                    //...
                   
                }
                else {
                    // 递减alive计数器
                    nbr->alive--;
                }
            }
        }
        
        pthread_mutex_unlock(&mospf_lock);

        // 如果有需要删除的邻居,释放所有待删除的邻居节点,由于邻居关系发生变化，更新序列号并发送新的LSU
        
        //...
            
            
            
        
    }
    
    return NULL;
}
```

### 4.检查数据库条目区（最发狂）

```c
//直接给全，要加锁
//调用自写函数：dump_mospf_db_entries，mospf_update_rtable
void *checking_database_thread(void *param)
{
    while (1) {
        sleep(1);
        pthread_mutex_lock(&mospf_lock);
        
        // 标记是否发生变化
        bool changed = false;
        
        // 安全遍历链表
        mospf_db_entry_t *db = (mospf_db_entry_t *)mospf_db.next;
        mospf_db_entry_t *q = (mospf_db_entry_t *)(mospf_db.next)->next;
        
        while (db != (mospf_db_entry_t *)&mospf_db) {
            // 递减存活时间
            db->alive--;
            
            // 检查是否超时
            if (db->alive < 1) {
                // 删除超时条目
                list_delete_entry(&db->list);
                free(db->array);
                free(db);
                changed = true;
            }
            
            // 移动到下一个条目
            db = q;
            q = (mospf_db_entry_t *)(q->list).next;
        }
        
        // 如果有条目被删除，更新数据库和路由表
        if (changed) {
            dump_mospf_db_entries();
            mospf_update_rtable();
        }
        
        pthread_mutex_unlock(&mospf_lock);
    }
    
    return NULL;
}
```

```c
//更新route_table,如下注释
//mospf_dijikstra为自写函数，clear_rtable,load_rtable在rtable.c里
void mospf_update_rtable(void)
{
    // 1. 用于存储Dijkstra算法计算出的路由表项
    struct list_head routing_table;
    init_list_head(&routing_table);

    // 2. 运行Dijkstra算法计算最短路径
    mospf_dijkstra(&routing_table);

    // 3. 清除当前路由表
    clear_rtable();

    // 4. 加载新的路由表
    load_rtable(&routing_table);
    //log(DEBUG, "loaded new routing table.\n");
}
```

```c
//调试MOSPF_database是否正确的输出函数，直接给出，但是后来我发现给了个print_rtable,所以你可以不用这个
void dump_mospf_db_entries(void)
{
    printf("MOSPF Database entries:\n");

    mospf_db_entry_t *db_entry;
    // 遍历数据库中的所有表项
    list_for_each_entry(db_entry, &mospf_db, list) {
        // 对每个表项,遍历其包含的所有LSA
        for (int i = 0; i < db_entry->nadv; i++) {
            struct mospf_lsa *lsa = &db_entry->array[i];
            
            // 按照格式打印:
            // router_id  subnet  mask  neighbor_rid
            fprintf(stdout, 
                "%hhu.%hhu.%hhu.%hhu\t%hhu.%hhu.%hhu.%hhu\t%hhu.%hhu.%hhu.%hhu\t%hhu.%hhu.%hhu.%hhu\n",
                // 打印router_id的4个字节
                (db_entry->rid >> 24) & 0xff,
                (db_entry->rid >> 16) & 0xff, 
                (db_entry->rid >> 8) & 0xff,
                db_entry->rid & 0xff,
                // 打印subnet的4个字节 
                (lsa->network >> 24) & 0xff,
                (lsa->network >> 16) & 0xff,
                (lsa->network >> 8) & 0xff,
                lsa->network & 0xff,
                // 打印mask的4个字节
                (lsa->mask >> 24) & 0xff,
                (lsa->mask >> 16) & 0xff,
                (lsa->mask >> 8) & 0xff,
                lsa->mask & 0xff,
                // 打印neighbor_rid的4个字节
                (lsa->rid >> 24) & 0xff,
                (lsa->rid >> 16) & 0xff,
                (lsa->rid >> 8) & 0xff,
                lsa->rid & 0xff);
        }
    }

    // 刷新输出缓冲区
    fflush(stdout);
}
```

<pre class="language-c"><code class="lang-c"><strong>//这里调用的函数均为自写函数
</strong><strong>//全局变量定义：（记得在void mospf_init()里初始化）
</strong>#define MAX_ROUTERS 256  // 最大路由器数量
static int nodes_sorted[MAX_ROUTERS];  // 存储按最短路径长度排序的节点
static int prev[MAX_ROUTERS]; 
static u32 gw_array[MAX_ROUTERS];            // 存储每个路由器对应的网关
static iface_info_t *rt_if_array[MAX_ROUTERS]; // 存储每个路由器对应的出接口
static u32 int_to_rid[MAX_ROUTERS];    // 整数到路由器ID的映射
static struct list_head rid_int_map[MAX_ROUTERS];   // 路由器ID到整数的映射
static int distances[MAX_ROUTERS];      // 最短距离数组
static int graph[MAX_ROUTERS][MAX_ROUTERS]; // 邻接矩阵
<strong>void mospf_dijkstra(struct list_head *routing_table)
</strong>{
    // 1. 初始化路由器ID到整数的映射
    int n = rid_int_map_init(rid_int_map);
    //log(DEBUG, "number of routers: %d\n", n);
    // 2. 根据LSDB初始化图结构
    init_graph(n);
    //log(DEBUG, "graph initialized.\n");
    
    // 3. 运行Dijkstra算法，源节点为0(当前路由器)
    dijkstra(n, 0);
   // log(DEBUG, "Dijkstra algorithm completed.\n");
    
    // 4. 打印最短路径的前驱数组
    printf("prev array:\n");
    for (int i = 0; i &#x3C; n; i++) {
        // 打印当前节点
        printf("%hhu.%hhu.%hhu.%hhu -> ",
            (int_to_rid[i] >> 24) &#x26; 0xff,
            (int_to_rid[i] >> 16) &#x26; 0xff,
            (int_to_rid[i] >> 8) &#x26; 0xff,
            int_to_rid[i] &#x26; 0xff);
        
        // 打印前驱节点
        if (prev[i] == -1) {
            printf("-\n");
        } else {
            printf("%hhu.%hhu.%hhu.%hhu\n",
                (int_to_rid[prev[i]] >> 24) &#x26; 0xff,
                (int_to_rid[prev[i]] >> 16) &#x26; 0xff,
                (int_to_rid[prev[i]] >> 8) &#x26; 0xff,
                int_to_rid[prev[i]] &#x26; 0xff);
        }
    }
    
    // 5. 将最短路径转换为路由表项
    convert_path_to_rtable(n, routing_table);
    //log(DEBUG, "converted path to routing table.\n");
    // 6. 打印计算得到的路由表
    printf("calculated routing table entries:\n");
    rt_entry_t *rt_entry;
    list_for_each_entry(rt_entry, routing_table, list) {
        printf("%x\t%x\t%x\t%s\n",
            rt_entry->dest,
            rt_entry->mask,
            rt_entry->gw,
            rt_entry->if_name);
    }
}
</code></pre>

```c
//1.记录路由器总数
//2.初始化rid_int_map的所有链表头
//3.首先添加本路由器的ID，然后遍历LSDB，添加LSU源路由器的ID和该路由器所有LSA中的邻居路由器ID
//4,调用自写函数rid_int_map_add（用于添加ID）和rid_int_map_lookup(用于查找ID)
int rid_int_map_init(struct list_head *rid_int_map)
{
    // 记录路由器总数
    int num = 0;

    // 初始化所有链表头
    //...

    // 首先添加本路由器的ID
    //...

    // 遍历LSDB
    mospf_db_entry_t *db_entry;
    list_for_each_entry(db_entry, &mospf_db, list) {
        // 添加LSU源路由器的ID
        //...

        // 添加该路由器所有LSA中的邻居路由器ID
        //...
        }
    }

    return num;
}
```

```c
//调用哈希函数进行create_node,struct rid如下，定义在mospf_daemon.h里
struct rid_int{
    struct list_head list;
    u32 rid;
    int int_id;
};
u8 hash8(char *addr, int len)
{
    //具体哈希函数自行定义，尽量避免碰撞
}
void rid_int_map_add(struct list_head *rid_int_map, u32 rid, int *int_id)
{
    // 计算哈希值作为数组索引
    u8 key = hash8((char *)&rid, 4);
    
    // 创建新节点
    struct rid_int *node = malloc(sizeof(struct rid_int));
    memset(node, 0, sizeof(struct rid_int));
    
    // 设置节点数据
    node->rid = rid;
    node->int_id = *int_id;
    (*int_id)++;
    
    // 将节点添加到对应的链表头
    struct list_head *list = &rid_int_map[key];
    list_add_head(&node->list, list);
}
int rid_int_map_lookup(struct list_head *rid_int_map, u32 rid)
{
    // 如果rid为0，直接返回0
    if (rid == 0) {
        return 0;
    }

    // 计算rid的哈希值作为数组索引
    u8 key = hash8((char *)&rid, 4);

    // 遍历对应链表中的所有节点
    struct rid_int *node;
    list_for_each_entry(node, &rid_int_map[key], list) {
        if (node->rid == rid) {
            return node->int_id;  // 找到匹配的rid，返回对应的整数索引
        }
    }

    return -1;  // 未找到匹配的rid
}
```

```c
// 1. 初始化邻接矩阵graph[i]为0
//2.设置当前路由器ID(int_to_rid[0])的映射(索引0)
//// 3. 遍历LSDB构建邻接矩阵,调用rid_int_map_lookup获取源路由器的索引,记录源路由器ID的映射,遍历该路由器的所有邻居,
//排除无效邻居后，获取目标路由器的索引，记录目标路由器ID的映射，在邻接矩阵中标记双向连接。
void init_graph(int num)
{
    // 1. 初始化邻接矩阵为0
    //...

    // 2. 设置当前路由器ID的映射(索引0)
    //...

    // 3. 遍历LSDB构建邻接矩阵
    mospf_db_entry_t *db_entry;
    list_for_each_entry(db_entry, &mospf_db, list) {
        // 获取源路由器的索引
        //...

        // 记录源路由器ID的映射
        //...

        // 遍历该路由器的所有邻居
        for (int i = 0; i < db_entry->nadv; i++) {
            if (db_entry->array[i].rid != 0) {  // 排除无效邻居
                // 获取目标路由器的索引
                //...
                // 记录目标路由器ID的映射
                //...

                // 在邻接矩阵中标记双向连接
                //...
            }
        }
    }
}
```

```c
void convert_path_to_rtable(int num, struct list_head *routing_table)
{
    // 初始化路由表
    init_list_head(routing_table);

    // 先添加直连网络
    iface_info_t *iface;
    list_for_each_entry(iface, &instance->iface_list, list) {
        rt_entry_t *entry = new_rt_entry(
            iface->ip & iface->mask,    // 目的网络
            iface->mask,                // 子网掩码
            0,                          // 直连网络网关为0
            iface                       // 出接口
        );
        list_add_tail(&entry->list, routing_table);
    }

    // 初始化网关和接口数组gw_array,rt_info_array
   //..

    // 遍历所有路由器(跳过索引0，即当前路由器)
    for (int i = 1; i < num; i++) {
        int curr = nodes_sorted[i];

        // 如果是直接邻居,查找邻居的IP地址和出接口
        //...
        // 如果不是直接邻居，继承前驱节点的网关和接口
        //...
        // 获取当前路由器的LSA信息
       //...

        // 处理该路由器的所有网络
        for (int j = 0; j < db_entry->nadv; j++) {
            u32 dest = db_entry->array[j].network & db_entry->array[j].mask;
            u32 mask = db_entry->array[j].mask;

            // 如果该网络尚未在路由表中且有有效出接口
            if (!dest_in_rtable(routing_table, dest, mask) && rt_if_array[curr]) {
                rt_entry_t *entry = new_rt_entry(
                    dest,                   // 目的网络
                    mask,                   // 子网掩码
                    gw_array[curr],         // 网关
                    rt_if_array[curr]       // 出接口
                );
                list_add_tail(&entry->list, routing_table);
            }
        }
    }
}
```

```c
// Dijkstra最短路径算法实现（自行实现，不提供帮助）
void dijkstra(int num, int src)

```

```c
//你可能会用到的辅助函数
// 辅助函数：查找路由器ID对应的IP地址和出接口
static u32 find_rid_ip(u32 rid, iface_info_t **if_out)
{
    iface_info_t *iface;
    list_for_each_entry(iface, &instance->iface_list, list) {
        mospf_nbr_t *nbr;
        list_for_each_entry(nbr, &iface->nbr_list, list) {
            if (nbr->nbr_id == rid) {
                *if_out = iface;
                return nbr->nbr_ip;
            }
        }
    }
    return 0;
}

// 辅助函数：查找rid对应的数据库条目
static mospf_db_entry_t *find_rid_db_entry(u32 rid)
{
    mospf_db_entry_t *db_entry;
    list_for_each_entry(db_entry, &mospf_db, list) {
        if (db_entry->rid == rid) {
            return db_entry;
        }
    }
    return NULL;
}

// 辅助函数：检查目的网络是否已在路由表中
static int dest_in_rtable(struct list_head *routing_table, u32 dest, u32 mask)
{
    rt_entry_t *entry;
    list_for_each_entry(entry, routing_table, list) {
        if (entry->dest == dest && entry->mask == mask) {
            return 1;
        }
    }
    return 0;
}
```

### 5.handle\_lsu报文部分

```c
//1.获取LSU消息头（ip头用packet_to_ip_hdr,还有mospf头，lsu头）
//2.校验mospf检验和,没有问题就获取发送方的router id和序列号（忽略来自自己的LSU）
//3.在数据库中查找此路由器的条目(自写函数find_mospf_db_entry)，更新数据库（自己写的函数update_mospf_db_via_lsu），减少TTL，checksum并转发（forward_mospf_lsu和send_mospf_lsu有区别），更新路由表(自写函数mospf_update_rtable，前面有)
void handle_mospf_lsu(iface_info_t *iface, char *packet, int len)
{
    // 获取 LSU 消息头
    //...

    // 校验检验和
    //...

    // 获取发送方的router id
    //...
    
    // 如果LSU来自自己，直接忽略
    //...

    // 获取序列号
    //...
    
    // 在数据库中查找此路由器的条目
    mospf_db_entry_t *db_entry = find_mospf_db_entry(iface, rid);
        // 更新数据库
        update_mospf_db_via_lsu(db_entry, iface, rid, lsu);

        // 减少TTL并转发
        lsu->ttl--;
        mospf->checksum = mospf_checksum(mospf);
        
        if (lsu->ttl > 0) {
            forward_mospf_lsu(iface, packet, len);
        }

        // 更新路由表
        mospf_update_rtable();
}
```

```c
//遍历MOSPF数据库中的所有条目,如果找到匹配的router ID，返回该条目;未找到匹配条目，返回NULL
mospf_db_entry_t *find_mospf_db_entry(iface_info_t *iface, u32 rid){}

//如果是新条目，db_entry创建新条目;否则更新现有条目
//调用了一个自写函数copy_lsa_ntoh
//注意善用宏
void update_mospf_db_via_lsu(mospf_db_entry_t *db_entry, iface_info_t *iface, 
    u32 rid, struct mospf_lsu *lsu)
{
u16 seq = ntohs(lsu->seq);
u32 nadv = ntohl(lsu->nadv);

if (!db_entry) {
// 创建新条目
//...
} else {
// 更新现有条目
//...
}

dump_mospf_db_entries();
}

// 复制并转换LSA数组（网络字节序到主机字节序）
struct mospf_lsa *copy_lsa_ntoh(struct mospf_lsa *array, int nadv)
{
struct mospf_lsa *new_array = malloc(nadv * sizeof(struct mospf_lsa));

for (int i = 0; i < nadv; i++) {
new_array[i].network = ntohl(array[i].network);
new_array[i].mask = ntohl(array[i].mask);
new_array[i].rid = ntohl(array[i].rid);
}

return new_array;
}
```

```c
//和send_mospf_lsu差不多，但有点不同
void forward_mospf_lsu(iface_info_t *iface, char *packet, int len)
{
    // 获取IP头部
    //...
    // 获取MOSPF头部
    //...

    // 检查TTL
    if (lsu->ttl <= 0) {
        return;
    }

    // 遍历所有接口
    iface_info_t *tx_iface;
    list_for_each_entry(tx_iface, &instance->iface_list, list) {
        // 跳过接收接口
        //...

        // 遍历该接口的所有邻居
        mospf_nbr_t *nbr;
        list_for_each_entry(nbr, &tx_iface->nbr_list, list) {
            // 为每个邻居创建一个新的数据包副本
            //...

            // 更新以太网头部(广播地址)
            //...

            // 更新IP头部
            //...

            // 发送数据包
            ip_send_packet(new_packet, len);
        }
    }
}
```

### 6.handle\_hello报文部分

<pre class="language-c"><code class="lang-c"><strong>//1.获取ip,mospf,hello报文头,发送方的IP地址，检查mospf校验和
</strong><strong>//2.检查子网掩码是否匹配
</strong><strong>//3.检查hello间隔是否为MOSPF_DEFAULT_HELLOINT,如果是，调用自写函数update_nbr_list_via_hello
</strong><strong>void handle_mospf_hello(iface_info_t *iface, char *packet, int len)
</strong>{
    //获取ip,mospf,hello报文头
    //...
    
    // 获取发送方的IP地址
    
        // 校验检验和
        //..

        // 检查子网掩码是否匹配
        //...

        // 检查hello间隔是否为5,如果是，调用自写函数update_nbr_list_via_hello
        //...

    }
}
</code></pre>

```c
//加锁
//1.先查找是否已存在该邻居,已存在则更新alive时间
//2.检查掩码是否匹配
//3.检查子网是否匹配
//4.创建新邻居,添加到邻居列表,更新序列号并发送LSU(send_mospf_lsu)
void update_nbr_list_via_hello(iface_info_t *iface, u32 rid, u32 ip, u32 mask, int helloint)
{
    pthread_mutex_lock(&mospf_lock);

    // 先查找是否已存在该邻居,已存在则更新alive时间
    //...

    // 检查掩码是否匹配
    //...

    // 检查子网是否匹配

    // 创建新邻居
    //...
    
    // 添加到邻居列表
    //...

    // 更新序列号并发送LSU
    //...

    pthread_mutex_unlock(&mospf_lock);
}
```

***

## 经验和一些碎碎念

1.不要检查是否需要更新数据库，收到报文就更新是一定对的，不要画蛇添足，否则可能你收到的hello和lsu报文都被忽略了，然后checking\_nbr\_thread和checking\_database\_thread以为某个邻居似了就把条目删除了，那你就ping不通了。

2.不要打太多log,在提交之前把log都注释了，要不会跑的特别慢，本来测试就会持续3-5分钟，你再加log更慢了，我有一次跑了20分钟……

以及你的log如果写错了可能Make不会报错，但是可能会seg fault，注意下。

但是log还是非常有用的，帮你知道函数调用路径以及发生了什么，收到了什么，发送了什么。

3.关于oj测试，第一个测试(simple\_ping\_test)OJ系统是等待OSPF40s收敛后h1 ping 10.0.6.22 -c 2 -w 2000（发两个ping报文，总计回复小于2000ms通过），第二个测试(unlink\_ping\_test)OJ会先r1 unlink r2/r3后再做和第一个测试一样的ping;第三个switch\_topo\_test只是一个更复杂的拓扑，能ping通就过。

4.自己的测试，如果你自己的topo.py能ping通，各个路由器的database显示如下，就是对的（指的是你四个thread都开启，把checking\_neighbor和checking\_database两个thread给注释了你是能ping通，但你在自己骗自己，没法unlink更新），一切以oj结果为准。

<figure><img src="/files/NJBw1bKVqkvvKqRH1gWa" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/3EXQhGM2aROWua7QHcub" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/FMaJ9rYbzpwj4Queb126" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/XCO9cq1ierjQhTae0dI5" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EQ48pqgAZmgJtgmjLXmp" alt=""><figcaption></figcaption></figure>

5.如果你找不到令人崩溃的crash/错误出现在哪里，可以采用如下方式：

a.注释掉mospf的部分检查实验五

b.注释掉部分thread和部分handle,比如你可以先把checking\_nbr\_thread,checking\_database\_thread注释了，其他保留，先看看这样能不能ping通，最起码保证send和处理是对的。

c.抓包

6.不要怀疑框架的正确性

7.当你实在想不出来时，放过自己，明天再想（）

***

{% hint style="warning" %}
禁忌的事
{% endhint %}

如果你上jyy的OS课，你应该知道现在的反编译工具可不像网安实验的IDA\_pro那样原始了，他们甚至可以推测源代码（比如ghidra）,而我们又有一份reference,于是---

如果你用像ghidra去反编译reference,你会发现你能看到所有函数的C语言实现（当然，你学过计基，你知道这个不能直接copy,不是直接可执行的，也不是原来的顺序，只是推测的）

<figure><img src="/files/E9Buyh88wOzKn3BSf7Bg" alt=""><figcaption><p>你显然不会认为这堆玩意可以直接copy</p></figcaption></figure>

所以吧，我觉得你可以看，去学习它的架构，你要是能看懂反汇编代码，那你肯定是会了，要不你都de不出来bug。

但是这里还是给助教一个建议，以后把reference做个加密。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blueroaring-njus-organization.gitbook.io/njucs_25spring_network-exp_additional/shi-yan-qi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
