Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.1. IPモジュール

IPモジュールを移植します。IPモジュールは他のプロトコル(ARP・ICMP・UDP・TCP)から参照される土台になるので、後半で最初に移植します。

IPモジュールのコードのコピー

IPモジュールのコードをmicropsからコピーします。

$ cp $MICROPS/ip.{h,c} kernel/net/

コピーしたら、2種類の修正を加えます。

1つ目は、ip.hIP_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_MAXIP_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.cip_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.ostring.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()はコメントアウト済みなので応答は生成されません)。