网页:https://pdos.csail.mit.edu/6.S081/2023/labs/net.html
任务1:In this lab you will write an xv6 device driver for a network interface card (NIC). (doing)
你将使用一种名为E1000的网络设备来处理网络通信。对于xv6(以及你编写的驱动程序)来说,E1000看起来就像一块连接到真实以太局域网(LAN)的真实硬件。实际上,你的驱动程序将要与之通信的E1000是由qemu提供的模拟设备,连接到一个同样由qemu模拟的局域网。在这个模拟的局域网上,xv6("客户机")的IP地址是10.0.2.15。Qemu还安排了运行qemu的计算机在局域网上显示为IP地址10.0.2.2。当xv6使用E1000向10.0.2.2发送数据包时,qemu会将数据包传递到运行qemu的(真实)计算机("主机")上的相应应用程序。
你将使用QEMU的"用户模式网络栈"。QEMU的文档中有更多关于用户模式网络栈的信息,可以在这里找到。我们已经更新了Makefile,以启用QEMU的用户模式网络栈和E1000网卡。
Makefile 配置了 QEMU,将所有传入和传出的数据包记录到你的实验目录中的 packets.pcap 文件中。查看这些记录可能有助于确认 xv6 是否正在发送和接收你预期的数据包。要显示记录的数据包,可以使用以下命令:
bash
tcpdump -XXnr packets.pcap
我们为本次实验向 xv6 仓库添加了一些文件。文件 kernel/e1000.c 包含了 E1000 的初始化代码以及用于发送和接收数据包的空函数,你需要填写这些函数。文件 kernel/e1000_dev.h 包含了 E1000 定义的寄存器和标志位的定义,这些内容在 Intel E1000 软件开发手册中有详细描述。文件 kernel/net.c 和 kernel/net.h 包含了一个简单的网络栈,实现了 IP、UDP 和 ARP 协议。这些文件还包含了一个用于存储数据包的灵活数据结构,称为 mbuf。最后,文件 kernel/pci.c 包含了在 xv6 启动时在 PCI 总线上搜索 E1000 网卡的代码。
这一次 LAB 讲义的内容太多了,我们直接看源码吧。
从讲义来看,这一次要通过的测试为 nettests,需要先在一个窗口运行 make server,再启动 xv6,运行 nettests
我们运行后陷入卡死状态,如下:
OK,那就先来看 nettests.c 源码,先从 main 看起:
c
int
main(int argc, char *argv[])
{
int i, ret;
// 这个端口号使用 -DNET_TESTS_PORT 在编译阶段定义
uint16 dport = NET_TESTS_PORT;
printf("nettests running on port %d\n", dport);
// 测试 ping 命令是否能运行正常
printf("testing ping: ");
ping(2000, dport, 1);
printf("OK\n");
// 测试单进程 100 次 ping
printf("testing single-process pings: ");
for (i = 0; i < 100; i++)
ping(2000, dport, 1);
printf("OK\n");
// 申请 10 个 子进程一起 ping
printf("testing multi-process pings: ");
for (i = 0; i < 10; i++){
int pid = fork();
if (pid == 0){
ping(2000 + i + 1, dport, 1);
exit(0);
}
}
for (i = 0; i < 10; i++){
wait(&ret);
if (ret != 0)
exit(1);
}
printf("OK\n");
// 测试 DNS
printf("testing DNS\n");
dns();
printf("DNS OK\n");
// 上述都没问题,则所有测试通过
printf("all tests passed.\n");
exit(0);
}
从 main 函数来看,我们需要关心的只有两个函数 ping() 和 dns()。
先来看 ping():
c
static void
ping(uint16 sport, uint16 dport, int attempts)
{
int fd;
char *obuf = "a message from xv6!";
uint32 dst;
// 10.0.2.2, which qemu remaps to the external host,
// i.e. the machine you're running qemu on.
dst = (10 << 24) | (0 << 16) | (2 << 8) | (2 << 0);
// you can send a UDP packet to any Internet address
// by using a different dst.
if((fd = connect(dst, sport, dport)) < 0){
fprintf(2, "ping: connect() failed\n");
exit(1);
}
for(int i = 0; i < attempts; i++) {
if(write(fd, obuf, strlen(obuf)) < 0){
fprintf(2, "ping: send() failed\n");
exit(1);
}
}
char ibuf[128];
int cc = read(fd, ibuf, sizeof(ibuf)-1);
if(cc < 0){
fprintf(2, "ping: recv() failed\n");
exit(1);
}
close(fd);
ibuf[cc] = '\0';
if(strcmp(ibuf, "this is the host!") != 0){
fprintf(2, "ping didn't receive correct payload\n");
exit(1);
}
}
大致来看,就是使用 connect 系统调用去连接 localhost(outside of qemu) 的一个端口,然后发送一个消息 "a message from xv6!",再接受一个消息 "this is the host!"
TODO: here 我们来仔细研究一下,ping命令这个过程会有哪些包的来往
TODO: here DNS