4.2. ARPモジュール
ARPモジュールを移植します。ARPはIPアドレスからMACアドレスを解決するプロトコルで、これを追加するとユニキャスト宛のIPパケットを送信できるようになります。ARPはタイマー機能を利用するため、あわせてプラットフォーム依存のタイマー関連コードも用意します。
ARPモジュールのコードのコピー
ARPモジュールのコードをmicropsからコピーします。修正は不要で、そのまま利用できます。
$ cp $MICROPS/arp.{h,c} kernel/net/
タイマー機能の追加
ARPモジュールはキャッシュの有効期限管理のために、初期化時にタイマー(timer_register())を登録します。この呼び出しでビルドと実行が通るように、プラットフォーム依存のタイマー関連コードを追加します。ただし中身は空のスタブで、timer_register()は渡された登録内容を保持せず成功(0)を返すだけです。タイマーを駆動する仕組みも用意しないため、この段階では周期的な処理は一切行われません(ARPキャッシュの有効期限管理は働きませんが、以降の動作確認には支障ありません)。実際にタイマーを機能させるのは応用課題で扱います。
📝 kernel/net/platform/xv6-riscv/timer.h
#ifndef TIMER_H
#define TIMER_H
#include <sys/time.h>
extern int
timer_register(struct timeval interval, void (*handler)(void));
extern int
timer_init(void);
extern int
timer_run(void);
extern int
timer_shutdown(void);
#endif
📝 kernel/net/platform/xv6-riscv/timer.c
#include <stdio.h>
#include <sys/time.h>
#include "platform.h"
#include "util.h"
int
timer_register(struct timeval interval, void (*handler)(void))
{
return 0;
}
int
timer_init(void)
{
return 0;
}
int
timer_run(void)
{
return 0;
}
int
timer_shutdown(void)
{
return 0;
}
arp.cはキャッシュのタイムスタンプ比較にtimersub()とtimercmp()のマクロを使うので、簡易libcのsys/time.hに追加します。
📝 kernel/net/platform/xv6-riscv/libc/sys/time.h
...
#include <time.h>
+#define timerclear(tv) ((tv)->tv_sec = (tv)->tv_usec = 0)
+
+#define timersub(a, b, res) \
+ do { \
+ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((res)->tv_usec < 0) { \
+ (res)->tv_sec--; \
+ (res)->tv_usec += 1000000; \
+ } \
+ } while (0)
+
+#define timercmp(a, b, CMP) \
+ (((a)->tv_sec == (b)->tv_sec) ? ((a)->tv_usec CMP (b)->tv_usec) : ((a)->tv_sec CMP (b)->tv_sec))
+
#endif
platform.hとplatform.cにタイマーの初期化・起動を組み込みます。
📝 kernel/net/platform/xv6-riscv/platform.h
...
#include "intr.h"
+#include "timer.h"
#endif
📝 kernel/net/platform/xv6-riscv/platform.c
...
intr_register(INTR_IRQ_SOFT, net_softirq_handler, 0, NULL);
+ if (timer_init() == -1) {
+ return -1;
+ }
return 0;
}
...
if (intr_run() == -1) {
return -1;
}
+ if (timer_run() == -1) {
+ return -1;
+ }
return 0;
}
...
IPモジュールからのアドレス解決の有効化
4.1で暫定的にコメントアウトしていたip.cのarp_resolve()呼び出しを有効に戻します。
📝 kernel/net/ip.c
...
#include "util.h"
#include "net.h"
-//#include "arp.h"
+#include "arp.h"
#include "ip.h"
...
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;
-// }
- return -1;
+ ret = arp_resolve(NET_IFACE(iface), target, hwaddr);
+ if (ret != ARP_RESOLVE_FOUND) {
+ return ret;
+ }
}
...
}
...
初期化関数の呼び出し
net.cでARPモジュールのヘッダをインクルードし、net_init()からarp_init()を呼び出すようにします。
📝 kernel/net/net.c
...
-//#include "arp.h"
+#include "arp.h"
...
int
net_init(void)
{
...
-// if (arp_init() == -1) {
-// errorf("arp_init() failure");
-// return -1;
-// }
+ if (arp_init() == -1) {
+ errorf("arp_init() failure");
+ return -1;
+ }
...
}
...
Makefileの修正
arp.oとtimer.oをビルド対象に追加します。
📝 Makefile
...
$N/ether.o \
$N/ip.o \
+ $N/arp.o \
$P/platform.o \
$P/intr.o \
+ $P/timer.o \
$P/driver/virtio_net.o \
...
動作確認
再ビルドした後、make qemuを実行してxv6を起動します。今度はユニキャストアドレス192.0.2.2に対してpingを実行します。
$ ping -c 3 192.0.2.2
xv6側のログに、ホストからのARP要求を受信して応答を返し(アドレス解決)、続いてICMP Echo要求を受信している様子が出力されます。
10:05:00.101 [D] net_input: dev=net0, type=0x0806, len=28 (kernel/net/net.c:232)
10:05:00.102 [D] arp_input: dev=net0, len=28 (kernel/net/arp.c:250)
10:05:00.103 [D] arp_cache_insert: INSERT: pa=192.0.2.1, ha=xx:xx:xx:xx:xx:xx (kernel/net/arp.c:185)
10:05:00.104 [D] arp_reply: dev=net0, len=28 (kernel/net/arp.c:224)
10:05:00.105 [D] net_input: dev=net0, type=0x0800, len=84 (kernel/net/net.c:232)
10:05:00.106 [D] ip_input: permit, dev=net0, iface=192.0.2.2 (kernel/net/ip.c:377)
Note
pingへの応答(ICMP Echo Reply)はまだ返りません。ICMPモジュールを移植していないため、Echo要求はIPモジュールまで届いた後、対応するプロトコルが見つからず破棄されます。次の4.3で応答できるようになります。