4.1. IPモジュール
IPモジュールを移植します。IPモジュールは他のプロトコル(ARP・ICMP・UDP・TCP)から参照される土台になるので、後半で最初に移植します。
IPモジュールのコードのコピー
IPモジュールのコードをmicropsからコピーします。
$ cp $MICROPS/ip.{h,c} kernel/net/
コピーしたら、2種類の修正を加えます。
1つ目は、ip.hのIP_TOTAL_SIZE_MAXの縮小です。ip_output()は送信パケットをuint8_t buf[IP_TOTAL_SIZE_MAX]というスタック上のバッファに組み立てます。この定数の本来の値(UINT16_MAX = 64KB)は、拡張したカーネルスタックにも載りません。Ethernetで一度に送れるのはMTU(1500バイト)までであり、IPの断片化もサポートしないため、この値をMTUまで縮めて対処します。
📝 kernel/net/ip.h
#define IP_HDR_SIZE_MIN 20
#define IP_HDR_SIZE_MAX 60
-#define IP_TOTAL_SIZE_MAX UINT16_MAX /* maximum value of uint16 */
+#define IP_TOTAL_SIZE_MAX 1500 /* MTU */
#define IP_PAYLOAD_SIZE_MAX (IP_TOTAL_SIZE_MAX - IP_HDR_SIZE_MIN)
Note
IP_PAYLOAD_SIZE_MAXはIP_TOTAL_SIZE_MAXから算出されるので、ICMP・UDP・TCPが送信バッファに使うサイズも連動して小さくなります。これらのモジュールはこの定数のおかげで、いずれもそのまま移植できます。
2つ目は、まだ移植していないARPモジュールとICMPモジュールへの依存を一時的に取り除くことです。ip_output_device()が呼ぶarp_resolve()と、ip_input()が未知プロトコルに対して呼ぶicmp_output()をコメントアウトします。これらはそれぞれ4.2・4.3でコメントを解除します。
📝 kernel/net/ip.c
...
#include "util.h"
#include "net.h"
-#include "arp.h"
+//#include "arp.h"
#include "ip.h"
-#include "icmp.h"
+//#include "icmp.h"
...
static void
ip_input(const uint8_t *data, size_t len, struct net_device *dev)
{
...
/* unsupported protocol */
if (hlen + 8 <= total) {
/*
* It should not be sent in response to ICMP error messages,
* but ICMP is always registered and will not reach this point.
*/
- icmp_output(ICMP_TYPE_DEST_UNREACH, ICMP_CODE_PROTO_UNREACH, 0, data, hlen + 8,
- iface->unicast, hdr->src);
+// icmp_output(ICMP_TYPE_DEST_UNREACH, ICMP_CODE_PROTO_UNREACH, 0, data, hlen + 8,
+// iface->unicast, hdr->src);
}
}
...
static int
ip_output_device(struct ip_iface *iface, const uint8_t *data, size_t len, ip_addr_t target)
{
char addr[IP_ADDR_STR_LEN];
uint8_t hwaddr[NET_DEVICE_ADDR_LEN] = {};
- int ret;
+// int ret;
...
} else {
- ret = arp_resolve(NET_IFACE(iface), target, hwaddr);
- if (ret != ARP_RESOLVE_FOUND) {
- return ret;
- }
+// ret = arp_resolve(NET_IFACE(iface), target, hwaddr);
+// if (ret != ARP_RESOLVE_FOUND) {
+// return ret;
+// }
+ return -1;
}
...
}
...
不足しているlibc関数の追加
ip.cのip_endp_pton()はエンドポイント文字列の解析にstrrchr()を使用します。簡易libcに実装を追加します。
📝 kernel/net/platform/xv6-riscv/libc/string.c
#include <string.h>
char *
strrchr(const char *cp, int ch)
{
char *save;
char c;
for (save = (char *) 0; (c = *cp); cp++) {
if (c == ch) {
save = (char *) cp;
}
}
return save;
}
📝 kernel/net/platform/xv6-riscv/libc/string.h
...
extern char *
strncpy(char *s, const char *t, int n);
+extern char *
+strrchr(const char *cp, int ch);
#endif
初期化関数の呼び出し
net.cでIPモジュールのヘッダをインクルードし、net_init()からip_init()を呼び出すようにします(3.1でコメントアウトしていた分の解除です)。
📝 kernel/net/net.c
...
-//#include "ip.h"
+#include "ip.h"
...
int
net_init(void)
{
...
-// if (ip_init() == -1) {
-// errorf("ip_init() failure");
-// return -1;
-// }
+ if (ip_init() == -1) {
+ errorf("ip_init() failure");
+ return -1;
+ }
...
}
...
Makefileの修正
ip.oとstring.oをビルド対象に追加します。
📝 Makefile
...
$N/net.o \
$N/ether.o \
+ $N/ip.o \
$P/platform.o \
$P/intr.o \
$P/driver/virtio_net.o \
$L/stdio.o \
- $L/stdlib.o
+ $L/stdlib.o \
+ $L/string.o
...
IPインタフェースの登録(暫定対応)
IPモジュールを追加したことでIPインタフェースを登録できるようになりました。ここでは暫定的にvirtio-netドライバの初期化の中でIPインタフェース(192.0.2.2/24)の登録を済ませます。
📝 kernel/net/platform/xv6-riscv/driver/virtio_net.c
...
+#include "ip.h"
+
struct net_device *
virtio_net_init(void)
{
...
+ /* TODO: Temporary */
+ {
+ struct ip_iface *iface = ip_iface_alloc("192.0.2.2", "255.255.255.0");
+ if (iface) {
+ ip_iface_register(dev, iface);
+ }
+ }
return dev;
}
Note
このハードコードは暫定的なものです。応用課題の「インタフェース制御」を実装してユーザ空間からインタフェースにアドレスを設定できるようになったら削除します。
動作確認
再ビルドした後、make qemuを実行してxv6を起動します。起動ログから、タイプ0x0800(IP)のプロトコル登録、192.0.2.0/24宛の経路、net0への192.0.2.2インタフェースの登録が確認できます。
xv6 kernel is booting
2026/08/11 10:00:00
10:00:00.101 [I] net_init: initialize... (kernel/net/net.c:277)
10:00:00.102 [I] net_protocol_register: success, type=0x0800 (kernel/net/net.c:184)
10:00:00.103 [I] net_init: success (kernel/net/net.c:302)
10:00:00.104 [D] virtio_net_init: device found (kernel/net/platform/xv6-riscv/driver/virtio_net.c:396)
10:00:00.105 [I] net_device_register: success, dev=net0, type=0x0002 (kernel/net/net.c:58)
10:00:00.106 [I] ip_iface_register: dev=net0, 192.0.2.2, 255.255.255.0, 192.0.2.255 (kernel/net/ip.c:237)
10:00:00.107 [I] net_device_add_iface: success, dev=net0 (kernel/net/net.c:142)
10:00:00.108 [I] ip_route_add: 192.0.2.0/255.255.255.0 dev net0 src 192.0.2.2 (kernel/net/ip.c:136)
10:00:00.109 [I] net_run: startup... (kernel/net/net.c:311)
10:00:00.110 [I] net_device_open: dev=net0 (kernel/net/net.c:65)
10:00:00.111 [I] virtio_net_open: dev=net0, addr=52:54:00:12:34:56 (kernel/net/platform/xv6-riscv/driver/virtio_net.c:197)
10:00:00.112 [I] net_run: success (kernel/net/net.c:319)
hart 1 starting
hart 2 starting
init: starting sh
$
開発環境で別のシェルを開き、ブロードキャストアドレス192.0.2.255に対してpingを実行します。
$ ping -b -c 3 192.0.2.255
Note
宛先をブロードキャストアドレスにしているのは、ARPによるアドレス解決をスキップして即座にIPパケットを送信できるからです(ユニキャスト宛だとARPが必要ですが、まだ移植していません)。
pingへの応答は返りませんが、xv6側のログから受信したパケットがIPモジュールまで届いていることが確認できます。
10:00:05.101 [D] ip_input: permit, dev=net0, iface=192.0.2.2 (kernel/net/ip.c:377)
ICMPをまだ移植していないため、IPモジュールは「対応するプロトコルなし」として黙って破棄します(icmp_output()はコメントアウト済みなので応答は生成されません)。