次に送信処理を追ってみましょう。icmpタスクから転送されたメッセージは、ipタスクがkz_recv()で受け取り、ip_proc()からリスト6の「ip_send()」が呼ばれます。ここではIPヘッダを付加して、ethernetタスクに転送します。
static int ip_send(struct netbuf *pkt)
{
……
pkt->cmd = ETHERNET_CMD_SEND;
……
kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
……
}
ipタスクから転送されたメッセージは、ethernetタスクがkz_recv()で受け取り、ethernet_proc()からリスト7の「ethernet_send()」が呼ばれます。ethernet_send()では、まだ送信先のMACアドレスが不明なのでリスト7のif文の中に入り、そのままarpタスクに転送します。
static int ethernet_send(struct netbuf *pkt)
{
……
/* 送信先MACアドレスが不明なので,ARPタスクに転送して解決してもらう */
if (!memcmp(pkt->option.ethernet.send.dst_macaddr,
"\x00\x00\x00\x00\x00\x00", MACADDR_SIZE)) {
pkt->cmd = ARP_CMD_SEND;
kz_send(MSGBOX_ID_ARPPROC, 0, (char *)pkt);
return 1;
}
pkt->cmd = NETDRV_CMD_SEND;
……
kz_send(MSGBOX_ID_NETPROC, 0, (char *)pkt);
……
}
arpタスクは、リスト8のようになっています。メイン関数の「arp_main()」で、kz_recv()によってメッセージを受信し、「arp_proc()」が呼ばれ、送信パケットならば「arp_send()」が呼ばれます。arp_send()では、まずパケットを“MSGBOX_ID_ARPPKTLIST”というキューに保存し、解決済みのMACアドレスならば「arp_flush()」を呼ぶことで、そのMACアドレスを補填してからパケットをethernetタスクに渡して送信依頼します。MACアドレスが未解決ならば、パケットはキューイングしたままでARP Requestを送信し、解決を試みます。
static int arp_sendpkt(uint16 operation, uint8 macaddr[], uint32 ipaddr)
{
……
pkt->cmd = ETHERNET_CMD_SEND;
……
pkt->option.ethernet.send.type = ETHERNET_TYPE_ARP;
kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
……
}
……
static int arp_send(struct netbuf *pkt)
{
……
kz_send(MSGBOX_ID_ARPPKTLIST, 0, (char *)pkt);
……
addr = arp_getaddr(pkt->option.ethernet.send.dst_ipaddr);
if (addr) {
arp_flush(-1);
} else {
/* ARP request を送信 */
arp_sendpkt(ARP_OPERATION_REQUEST, NULL,
pkt->option.ethernet.send.dst_ipaddr);
}
……
}
static int arp_proc(struct netbuf *buf)
{
……
switch (buf->cmd) {
……
case ARP_CMD_RECV:
ret = arp_recv(buf);
break;
case ARP_CMD_SEND:
ret = arp_send(buf);
break;
……
}
int arp_main(int argc, char *argv[])
{
……
while (1) {
kz_recv(MSGBOX_ID_ARPPROC, NULL, (char **)&buf);
ret = arp_proc(buf);
……
}
リスト9は、ARPパケットを受信した場合のarpタスクの処理です。「arp_recv()」が呼ばれてMACアドレス解決され、さらにarp_flush()が呼ばれることで“MSGBOX_ID_ARPPKTLIST”に送信保留されているパケットがethernetタスクに転送され、送信処理が再開されます。
static void arp_flush(int count)
{
……
while (1) {
kz_recv(MSGBOX_ID_ARPPKTLIST, NULL, (char **)&pkt);
……
addr = arp_getaddr(pkt->option.ethernet.send.dst_ipaddr);
……
memcpy(pkt->option.ethernet.send.dst_macaddr, addr->macaddr, MACADDR_SIZE);
pkt->cmd = ETHERNET_CMD_SEND;
kz_send(MSGBOX_ID_ETHPROC, 0, (char *)pkt);
……
}
……
static int arp_recv(struct netbuf *pkt)
{
……
switch (arphdr->operation) {
case ARP_OPERATION_REPLY:
case ARP_OPERATION_REQUEST:
……
addr = arp_setaddr(ipaddr, arphdr->sender_macaddr);
arp_flush(-1);
if (arphdr->operation == ARP_OPERATION_REPLY)
break;
……
/* ARP reply を返送 */
arp_sendpkt(ARP_OPERATION_REPLY, addr->macaddr, addr->ipaddr);
……
}
ここでリスト7をもう一度見てください。送信再開によって、arpタスクからethernetタスクに渡されたパケットは、今度はarpタスクによってMACアドレスが補填されているため、ethernet_send()のif文には入らずに、netdrvタスクに対して転送されています。
リスト10は、netdrvタスクの送信処理部分です。メッセージは「netdrv_main()」のkz_recv()によって受信し、「netdrv_proc()」が呼ばれて実際の送信処理が行われます。
/* スレッドからの要求を処理する */
static int netdrv_proc(struct netbuf *buf)
{
switch (buf->cmd) {
……
case NETDRV_CMD_SEND: /* イーサネットへのフレーム出力 */
rtl8019_send(0, buf->size, buf->top);
……
}
int netdrv_main(int argc, char *argv[])
{
……
kz_setintr(SOFTVEC_TYPE_ETHINTR, netdrv_intr); /* 割り込みハンドラ設定 */
while (1) {
kz_recv(MSGBOX_ID_NETPROC, NULL, (char **)&buf);
netdrv_proc(buf);
……
}
最後に、KOZOS側からのICMP Echoの送信について説明します。icmpタスクは、ICMP Echoの“自分からの送信機能”も持っています。icmpタスクに“ICMP_CMD_SEND”というメッセージを送ることで、リスト11の「icmp_send()」が呼ばれ、ICMP Echoが送信されます。
static int icmp_send(struct netbuf *pkt)
{
……
icmp_sendpkt(pkt->option.icmp.send.ipaddr,
ICMP_TYPE_REQUEST, 0, id, i,
64 - sizeof(struct icmp_header), NULL);
……
}
リスト12は、コマンド処理タスク(command.c)に対する改造です。マイコンボードのコンソール経由でpingコマンドを実行することで、icmpタスクに対して“ICMP_CMD_SEND”をメッセージ送信し、ICMP Echoの送信を開始します。
/* pingの開始をicmpタスクに依頼する */
static void send_icmp()
{
……
buf->cmd = ICMP_CMD_SEND;
……
kz_send(MSGBOX_ID_ICMPPROC, 0, (char *)buf);
}
int command_main(int argc, char *argv[])
{
……
while (1) {
……
/* コンソールからの受信文字列を受け取る */
kz_recv(MSGBOX_ID_CONSINPUT, &size, &p);
……
} else if (!strncmp(p, "ping", 4)) { /* pingコマンド */
send_write("ping start.\n");
send_icmp();
……
}
マイコンボードで実際に動作させてみましょう。まずは、前述のWebサイトの「ソースコードはこちら」からソースコード(h8_05)をダウンロードし、ビルドします。ビルドと起動の手順は前回までと同様です(ただし部分的に修正が必要です。詳しくはWebサイトの方を参照してください)。
PCとマイコンボードをLANケーブルで接続します。接続にはクロスケーブルを用いるか、間にハブを入れる必要があります。
KOZOS側のIPアドレスは「192.168.10.16」、通信相手のIPアドレスは「192.168.10.1」に固定してあるので、PCのIPアドレスを「192.168.10.1」に設定します。この状態で、PCから「192.168.10.16」に対してpingを発行してみます(リスト13)。
hiroaki@letsnote:~>% ping 192.168.10.16 PING 192.168.10.16 (192.168.10.16): 56 data bytes 64 bytes from 192.168.10.16: icmp_seq=0 ttl=64 time=79.725 ms 64 bytes from 192.168.10.16: icmp_seq=1 ttl=64 time=59.871 ms 64 bytes from 192.168.10.16: icmp_seq=2 ttl=64 time=59.878 ms 64 bytes from 192.168.10.16: icmp_seq=3 ttl=64 time=59.790 ms 64 bytes from 192.168.10.16: icmp_seq=4 ttl=64 time=59.795 ms ^C --- 192.168.10.16 ping statistics --- 5 packets transmitted, 5 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 59.790/63.812/79.725/7.957 ms hiroaki@letsnote:~>%
おおっ! pingに応答できています。感動です! さらにマイコンボードでpingを実行して、KOZOS側から疎通確認をしてみましょう(リスト14)。
command> ping ping start. command> ICMP received: c0a80a01 00 00 08ec ICMP received: c0a80a01 00 00 08eb ICMP received: c0a80a01 00 00 08ea unknown. command>
こちらも問題なく通信できています。「Wireshark」などでキャプチャーしてみると、MACアドレス解決されて、ICMPパケットが流れている様子が確認できます。
今回はネットワーク機能の“序盤”として、pingによる疎通が行えるようにプログラムを実装しました。次回はいよいよTCP/IPを実装し、本連載の最終目標である「フルスクラッチの自作Webサーバ」を動作させます。ご期待ください! (次回に続く)
坂井弘亮(さかいひろあき)
某企業でネットワーク系の開発を行いながら、個人で組み込みOS「KOZOS」の開発や書籍/雑誌記事執筆、各種セミナーでの発表やイベント出展、勉強会の開催など多方面で活動中。IPAセキュリティ&プログラミングキャンプ講師(2010〜)。著書は「12ステップで作る組込みOS自作入門」など多数。
Copyright © ITmedia, Inc. All Rights Reserved.
組み込み開発の記事ランキング
コーナーリンク
よく読まれている編集記者コラム