■ENC28J60を使ったイーサネット接続デバイスは受信がうまく行くことは確認できて、そろそろやりとりを始めたい。ただ、いきなりTCPだと3回握手しなければならいのとシーケンスやらセッション管理やらが必要で敷居が高いのでそれはやめておいて、まずはpingに応答を返すところから。pingのパケット受信は前回できていたしね。
たいていのまっとうなネットワーク技術説明系のウェブサイトでは説明がレイヤに分かれているのが普通なんですが、そういうところから入ると実際にネットワーク上を流れるパケット全体のイメージというのは却って掴みづらいものですね。歳は取りたくないものです。
pingはICMPに乗っかってネットワークを流れるわけですが、じゃあICMPはどのようにカプセル化されているかというとちょっとうろ覚え。
MACフレームの中のIPパケットの中にICMPパケットが乗っているというのが正解。MACフレームヘッダにもIPパケットヘッダにもあて先、差出元のアドレスが書き込まれていて、応答時はあて先、差出元をひっくり返して転記して、IMCPパケットのコードを応答コード(0)に書き換え、ICMPパケット全体のチェックサムを計算して設定して、後は送信。MACフレームのチェックサムはENC28J60側にお任せなのでそちらは放っておく。
イーサネット上でのチェックサムは「1の補数」形式という解ったような解らないような方式なんですが、実装レベルではパケット全体を2バイト単位で区切って、2バイトごとにキャリー付加算するということをやります。
アセンブラだとコードは簡単に思いつくんですが、Cだとちょっとめんどい。2バイトだからunsigned shortを使えば簡単かなと思わないでもないんですが、計算はビックエンディアンで行う必要があり、実際の計算ライブラリはリトルエンディアンですからストレートにはいかない。何を言っているかといえば、ICMPのデータはunsigned charの連続体によって確保されるデータ領域に存在していて、そこから2バイトずつ読み取り、ただしエンディアンを変換してunsigned shortに見立てなければならないわけです。
コードを出せば全て解決なんですが、テクニック的には単純なのでキモだけ。
unsigned char2バイトとunsigned shortの共用体を使いました。
typedef union { unsigned char c[2]; unsigned short s; } transShort;
バッファを2バイト単位で読み取り、ただし、配列cには逆順に格納すれば、リトルエンディアンに変換されたshortが手に入るわけです。キャリーを取るので、実際の加算器にはunsigned longを使っています。こちらも同様にunsigned long1つとunsigned short2つの共用体を使い、オーバーフロー分を簡単に取り出せるようにしています(上位2バイトに相当するshortがそのままキャリー回数となる)。
ICMPのECHO応答を返して、PC側で受信を確認。ここで欲が出てARP応答も返すように実装する。今はPC側でarpコマンドでIPテーブルとMACアドレスの変換テーブルを強制的に置いている(ルータはENC28J60のデバイスを知らないので、arpテーブルを用意しないとPCとは通信できない)のだけど、これをARP応答を返すようにすれば、arpコマンドで事前準備する手間は省ける。
ARPはIPに並ぶレイヤのプロトコルですが、やりとりの内容はICMP echo要求/応答に似ています。送信元のアドレス(MACとIPアドレス)を送信先に転記し、送信元のアドレスに自分自身のアドレスを埋め込む。
送信先、送信元の入れ替えをしないのは、ARPの要求は誰に送ればわからない状態で行われるので、ブロードパケットとして送られ、送信先のアドレスは匿名状態だからです。まあ、理屈を考えれば当然ですな。
これであたかもpingに自然に応答するデバイスが出来上がったわけですが、1点気になることが。
pingを延々と送り続けていると、1分くらいたつと送信が閉塞してしまう。受信はできて、応答動作はしているのだけど、送られていないっぽい。さらにその状態で送り続けると受信パケットが壊れ出す。アナログっぽい異常動作なのでブレッドボード上で動くように作ったことが原因なのか、電源にスイッチングを使っていることなのか、配線がむやみに長いことが原因なのか。アナログ系の不備なんじゃないかなあと思うのだけど、短い間でも動くことは動くのでプログラムのテストはできる。
とりあえずプログラムの方を進めていきたい。