★IP,ICMP,UDP のチェックサムの計算方法


概要

TCP/IPのIP, ICMP,UDP, TCP のヘッダにはそれぞれ、チェックサムがある。 TCPのヘッダはまだためしていないので、IP,ICMP,UDPのチェックサムの 計算方法を紹介する。

どのチェックサムも、ネットワークバイトオーダーの16ビットの数値を 読み出して1の補数和の1の補数をチェックサムとする点は共通だ。
チェックサムを計算する領域は、IPヘッダ、ICMPデータはそれぞれ、そのもの の範囲だが、UDPではUDPの領域に加えて、source, destinationの IPアドレス、プロトコル番号、データ長からなる擬似ヘッダを含める。

1の補数和(complement sum)の1の補数(complement)?

16bitの数値Aの1の補数は、0xffff-Aと定義されるが、bit反転でもある。 では、1の補数和とは1の補数の和のことなのかというと、そうではない。
数学的な背景はよくわからないが、1の補数和は普通の足し算の結果の MSB側の繰り上がりをLSBに加算することで計算できるらしい。
この足し算は、面白いことに、バイトオーダーに関して特別な配慮を必要しない。 なぜならば、桁あふれが巡回するからだ。 たとえばMSBの桁上がりが、LSBへ加算されることは、逆オーダーの下位8ビットから 上位8ビットへの桁上がりを実行していることになる。

計算のテクニックとして、このMSBの桁上がりはまとめてあとから加算することが できる。そのときに桁あふれが発生したら、それもまた加算する。

最後に1の補数和の結果の1の補数に変換してチェックサムとする。 リトルエンディアンのシステムでは、ここでバイトオーダを変換して、 それぞれのヘッダのチェックサムフィールドの値にする。

コーディング例

では、実際のコードをみてみよう。

unsigned short checksum(unsigned short *buf, int size)
{
	unsigned long sum = 0;

	while (size > 1) {
		sum += *buf++;
		size -= 2;
	}
	if (size)
		sum += *(u_int8_t *)buf;

	sum  = (sum & 0xffff) + (sum >> 16);	/* add overflow counts */
	sum  = (sum & 0xffff) + (sum >> 16);	/* once again */
	
	return ~sum;
}
見てのとおり、とくに特殊なテクニックは使用していない。
IPのヘッダのチェックサムやICMPのチェックサムでは、計算結果を格納する チェックサムフィールドに0をセットして、このルーチンを呼び、返り値を チェックサムフィールドに格納する。
例えば、
	ip->ip_sum = 0;
	ip->ip_sum = checksum((unsigned short *ip)ip, 4 * ip->ip_hl);

UDPの場合

UDPのチェックサムは、UDPヘッダとUDPデータに加えて、 source, destinationのIPアドレス、プロトコル番号、データ長からなる 擬似ヘッダをチェックサムの計算に含めなければならない。
UDP擬似ヘッダ
offset +0+1+2+3
+0src ip address
+4dst ip address
+80:zeroprotocolUDP data length

UDPのプログラムインターフェースの実装にもよるが、単にチェックサムを 計算したいだけならば、わざわざ擬似ヘッダを構成する必要はない。 擬似ヘッダに用いられるであろう要素を計算に含めるだけでよい。

unsigned short udpchecksum(struct ip *ip, struct udphdr *udp)
{
	unsigned long sum;
	u_int16_t *s;
	int size;
	u_int32_t addr;
	sum = 0;

	addr = ip->ip_src;
	sum += addr >> 16;
	sum += addr & 0xffff;
	addr = ip->ip_dst;
	sum += addr >> 16;
	sum += addr & 0xffff;
	sum += ip->ip_p << 8;	/* endian swap */
	size = udp->uh_ulen;
	sum += size;
	
	size = ntohs(size);
	s = (u_int16_t *)udp;
	while (size > 1) {
		sum += *s;
		s++;
		size -= 2;
	}
	if (size)
		sum += *(u_int8_t *)s;

	sum  = (sum & 0xffff) + (sum >> 16);	/* add overflow counts */
	sum  = (sum & 0xffff) + (sum >> 16);	/* once again */
	
	return ~sum;
}
これは、引数にIPヘッダとUDPヘッダへのポインタを与えて、UDPのチェックサムを 計算する関数だ。
ipヘッダのプロトコル番号フィールド(ip->ip_p)の加算で、8bitの左シフトをしているのは、リトルエンディアン専用のコードで、計算中のエンディアンを統一するために入れてある。 UDP擬似ヘッダの+8のアドレスで16bitの値を得る場合、リトルエンディアンのシステムでは、プロトコル番号は上位8bitに入らなければならない。 ところが、8bit型であるip_pで参照すると下位に入ってしまい、この部分だけビッグエンディアンで取得したものと同一になってしまう。 これを8bitシフトすることでリトルエンディアンの数値としている。 ビッグエンディアンのシステムではこの8bitシフトは必要ない。

関連情報

  1. 俺様, "IPのチェックサムは不要?", FENIX message #2053, Apr, 2001
  2. Braden, R., Borman, D., and C. Partridge, "Computing the Internet Checksum", RFC 1071, ISI, Cray Research, BBN Laboratories, September 1988

FENIX/ MEMBERS/ thomas's HOME/ memo/ ip/checksum

佐藤益弘 thomas@fenix.ne.jp