GOGATSU やぶ

【ICMP】【補数】IPプロトコルヘッダのチェックサム計算(1の補数和の1の補数)

 ICMPフォーマットのチェックサム計算実装までに必要だった周辺知識として、ICMPの概要、補数の理解、ビット操作によるデータの取り出しについてまとめておく。

※コード一部省略。

ICMP

1. 概要

 ICMPはネットワークの通信状態を確認する際に使用するプロトコル
 パケットの取りこぼしや、宛先に指定されたIPアドレスにたどり着けない場合に、エラーを検出し、報告する役割を持っている。

2. ICMPフォーマットの全体図

rfc792

pingコマンドで送信するICMPフォーマット(13ページ目)

f:id:ybsatsuki:20211230155821p:plain
RFC 792

ICMPヘッダのフォーマットとサイズの基本 | IT情報サイト ”ITアベイラボ”

f:id:ybsatsuki:20211230145827p:plain
IT アベイラボ ICMPヘッダのフォーマットとサイズの基本

 Type(タイプ)については、メッセージフォーマットのタイプが決められているみたい。
 エコー要求であれば8、エコー応答であれば0。
 他でいうと、今回の作業には関係ないが、例えばICMPリダイレクトに使用されるICMPメッセージフォーマットの場合、タイプは5。

ICMPリダイレクトとは

f:id:ybsatsuki:20211230155521p:plain
RFC 792

 RFCの説明書を読み進めた結果、Codeは0、Idとシーケンス番号は送信側が複数のICMPパケットの識別用に用いるためのデータと理解。

チェックサム

1. 概要

 IPプロトコルヘッダのデータを数値にし、全て加算した結果を送信することで、受信側でも同様にデータを加算した結果が合致していれば、データの抜け漏れがないと判断する。

2. チェックサムの計算方法

rfc1071

 チェックサムは、ICMP タイプから始まる ICMP メッセージの 16 ビット単位の 1 の補数の合計の、さらに 16 ビットの 1 の補数である。
 チェックサムを計算するとき、チェックサムフィールド自身はゼロと見なされるべきである。
 全体の長さが奇数の場合、チェックサムを計算するために値ゼロのオクテットがひとつ追加される。

つまり、
チェックサムの初期値は0
・1の補数和を計算し、さらにその値の1の補数をとる。
・全体の長さは、チェックサムの計算に合わせて偶数にする→16ビット単位で計算する。

と理解。

補数の定義から補数和まで

補数表現とは?1の補数と2の補数の違いと計算方法まとめ | サービス | プロエンジニア


1.「補数」とは

補数には、「基数の補数」と「減基数の補数」の二種類がある。

基数:10進数の場合は10、2進数の場合は2など、基準とする文字の個数。
減基数:基数-1の値。

f:id:ybsatsuki:20211231094528p:plain


2. 2の補数、1の補数

2進数における、
・「基数の補数」が2の補数
・「減基数の補数」が1の補数

f:id:ybsatsuki:20211231111022p:plain


3. 2の補数、1の補数の求め方

・2の補数は、元の数を反転させ、1を加算したもの。
・1の補数は、元の数を反転させたもの。

例)
・2の7乗
・8ビット表示

f:id:ybsatsuki:20211231111520p:plain


4. 2の補数和、1の補数和

1の補数 ‐ 通信用語の基礎知識

2の補数和では、桁上がりが生じた場合は、桁上がりを捨てる。
1の補数和では、桁上がりが生じた場合は、一番右の桁に足し込む。
1の補数では、+0の否定として、-0がある。
 0を表現する数が、1の補数の方が多いことから、表現範囲が2の補数と1の補数とでは異なってくる。

f:id:ybsatsuki:20211231114224p:plain

実装方法

1. ICMPフォーマット
struct IcmpPacket
{
  uint8_t type; //8
  uint8_t code; //10
  uint16_t check_sum; //0
  uint16_t id; //20
  uint16_t sn; //30
};
2. チェックサム計算 

(1) 1の補数和
データを加算した結果、0x33EBCだったと仮定する。
(例の値はこちらから)

① 加算後の状態
f:id:ybsatsuki:20211231115628p:plain

② 桁上がりした数(この場合3)を終端のデータに足したい
f:id:ybsatsuki:20211231120355p:plain

③そのために、
・①の状態から16ビット右シフトさせたデータ
・①の状態から下位16ビット分のデータ
を作る。

f:id:ybsatsuki:20211231121054p:plain

④下位16ビット分取り出すには、0xffffを被せる。

f:id:ybsatsuki:20211231121532p:plain

...

  //1の補数和
  while (sum>>16)
  {
    sum = (sum & 0xffff) + (sum >> 16);
  }

...

(2). 1の補数
1の補数は、元の値を反転させたもの。

...

  //1の補数(反転させる)
  uint16_t checksum = ~sum;

  return checksum;
}

出力結果

  std::cout << std::bitset<32>(0x33EBC) << std::endl;  
  //0000 0000 0000 0011 0011 1110 1011 1100

  std::cout << std::bitset<32>(icmp.check_sum) << std::endl;  
  //0000 0000 0000 0000 1100 0001 0100 0000

その他:uint8_tの値をstd::coutで出力したら、ASCIIコードに変換される。

c++ — uint8_tはcoutで印刷できません

 uint8_tはunsigned char型の別名であり、coutで出力しようとするとASCHIIコードで表示される。

   uint8_t icmp_type;
 
   icmp_type = 8;

   std::cout <<  icmp.type << std::endl;  //空白
   std::cout <<  (unsigned)icmp.type << std::endl;  //8