0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

/0 のサブネットマスクを 0xFFFFFFFF << 32 で作ったら 255.255.255.255 になった

0
Posted at

背景

自分のツールサイトに、IPアドレスとプレフィックス長からネットワークアドレスやホスト数を出すページを置いています。
https://hashitosystem.com/tools/subnet/
今朝その中身を読み返していたら、マスクを作る1行に p === 0 ? 0 : ... という特別扱いが入っていました。
自分で書いたはずなのに理由を覚えていなかったので、外したらどうなるか試しました(こういうのをコメントに残さないのが私の悪い癖です…)。

プレフィックス長とマスク

前提です。
IPv4 のアドレスは32ビットの数で、192.168.1.10/24 の /24 はプレフィックス長と呼ばれる。
先頭から何ビットがネットワーク部かを表していて、/24 なら上位24ビットが1のマスク、つまり 255.255.255.0 になります。
/0 は「全部ホスト部」なのでマスクは 0.0.0.0、/32 は 255.255.255.255 です。

素直に書いたマスク

全ビットが1の 0xFFFFFFFF を、ホスト部のビット数(32 − p)だけ左にずらせばマスクになる、と考えて書いたのが下記です。
dotted は32ビットの数を a.b.c.d の文字列にする関数で、ツールのものをそのまま使っています。

function dotted(n) {
  return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255].join('.');
}
const naiveMask = p => (0xFFFFFFFF << (32 - p)) >>> 0;

console.log(naiveMask(24), dotted(naiveMask(24))); // 4294967040 255.255.255.0
console.log(naiveMask(1),  dotted(naiveMask(1)));  // 2147483648 128.0.0.0
console.log(naiveMask(0),  dotted(naiveMask(0)));  // 4294967295 255.255.255.255

Node.js 24 で動かすと、/24 と /1 は合っているのに、/0 だけ 255.255.255.255 になりました。

シフト量は5ビットしか見ない

原因は、JavaScript のシフト演算子(<< >> >>>)が、シフト量の下位5ビットだけを使う仕様だからだ。
32 は2進数で 100000 なので、下位5ビットは 0 になり、x << 32 は x << 0、つまり何もずらさない計算になります。
実際に 1 << 32 は 1、1 << 33 は 2 でした。
32ビット以上ずらす場面を、JavaScript は「32で割った余りだけずらす」と決めている、と理解しています。

なのでツールでは、下記のように /0 だけ先に分けていました。

function maskInt(p) {
  return p === 0 ? 0 : (0xFFFFFFFF << (32 - p)) >>> 0;
}

p が1〜32なら、シフト量は0〜31に収まるので普通に計算できます。
特別扱いが必要なのは /0 の1か所だけです。

もう一つの罠、負の数

同じ関数の末尾についている >>> 0 にも理由がありました。
JavaScript のビット演算(& | ~ <<)は、結果を符号付きの32ビット整数で返すので、最上位ビットが1のアドレスは負の数になる。
192 は2進数で 11000000 なので、192.168.x.x はすべてこれに当たります。

下記は 192.168.1.10 を数にした ip と /24 のマスクで、ネットワークアドレスを出した例です。

const ip = ((192 * 256 + 168) * 256 + 1) * 256 + 10; // 3232235786
const mask = 0xFFFFFF00;                             // /24

console.log(ip & mask);                   // -1062731520
console.log((ip & mask) >>> 0);           // 3232235776
console.log(dotted(ip & mask));           // 192.168.1.0
console.log((ip & mask) === 3232235776);  // false

dotted の中では >>> を使っているので、負の数でも文字列にすると 192.168.1.0 と正しく出ます。
画面の表示は合っているのに、=== で比べると false になる、という壊れ方です。
>>> 0 は「0ビットだけ符号なしで右にずらす」演算で、値はそのままに、符号なしの32ビット整数(0〜4294967295)として読み直す、という意味になります。

どう付き合うか

JavaScript で IPv4 のマスクを作るときは、/0 だけ分岐で 0 を返し、ビット演算の結果には毎回 >>> 0 を付けて符号なしに戻す。
この2つを関数の中で済ませておけば、呼び出す側は普通の数として比べたり足したりできます。


本記事はAI補助で執筆した、個人開発の紹介記事です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?