ビット演算で理解するCIDR計算とサブネット分割の実装

はじめに
ネットワークエンジニアにとってCIDR表記は日常的に使うものですが、「なぜ/24は256個のIPアドレスなのか」「サブネット境界とは何か」を正確に理解している人は意外と少ないかもしれません。
この記事では、Next.js + TypeScriptでCIDRチェッカーを実装しながら、ビット演算を使ったIPアドレス計算の本質を学んでいきます。
ツールを実際に試す:TechTools - CIDRチェッカー
CIDRとは何か
CIDR(Classless Inter-Domain Routing)は、IPアドレスをネットワーク部とホスト部に分割する表記法です。
192.168.1.0/24
↑
プレフィックス長(ネットワーク部のビット数)
/24は「上位24ビットがネットワーク部、残り8ビットがホスト部」を意味します。
192.168.1.0/24
= 11000000.10101000.00000001.00000000
^^^^^^^^ ^^^^^^^^ ^^^^^^^^ ^^^^^^^^
ネットワーク部(24ビット) ホスト部(8ビット)
設計の核心:なぜ数値として扱うのか
IPアドレスを文字列で扱うと計算が複雑になります。そこで、32ビット整数に変換します。
const ipToNumber = (ip: string): number => {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
};
この関数の動作を詳しく見ましょう。
入力: '192.168.1.1'
ステップ1: '192' → 192 (0x000000C0)
ステップ2: 192 << 8 = 49152 (0x0000C000)
+ 168 = 49320 (0x0000C0A8)
ステップ3: 49320 << 8 = 12625920 (0x00C0A800)
+ 1 = 12625921 (0x00C0A801)
ステップ4: 12625921 << 8 = 3232235776 (0xC0A80100)
+ 1 = 3232235777 (0xC0A80101)
結果: 3232235777
>>> 0の意味は、JavaScriptの数値を符号なし32ビット整数に変換することです。
サブネットマスクの生成
プレフィックス長からサブネットマスクを計算します。
const prefix = 24;
const mask = (0xFFFFFFFF << (32 - prefix)) >>> 0;
// => 0xFFFFFF00 = 255.255.255.0
ビット演算の流れを可視化します。
/24の場合:
0xFFFFFFFF = 11111111 11111111 11111111 11111111
<< 8 = 11111111 11111111 11111111 00000000
= 0xFFFFFF00
= 255.255.255.0
/26の場合:
0xFFFFFFFF = 11111111 11111111 11111111 11111111
<< 6 = 11111111 11111111 11111111 11000000
= 0xFFFFFFC0
= 255.255.255.192
ネットワークアドレスの計算
ネットワークアドレスは、IPアドレスとサブネットマスクのAND演算で求まります。
const calculateCIDR = (input: string): CIDRInfo => {
const [ip, prefix] = input.split('/');
const prefixNum = parseInt(prefix, 10);
const ipNum = ipToNumber(ip);
const mask = (0xFFFFFFFF << (32 - prefixNum)) >>> 0;
const wildcard = ~mask >>> 0;
const network = ipNum & mask;
const broadcast = network | wildcard;
const firstIP = network + 1;
const lastIP = broadcast - 1;
const totalHosts = Math.pow(2, 32 - prefixNum);
const usableHosts = Math.max(0, totalHosts - 2);
return {
network: numberToIp(network),
broadcast: numberToIp(broadcast),
fullRange: `${numberToIp(network)} ~ ${numberToIp(broadcast)}`,
hostRange: `${numberToIp(firstIP)} ~ ${numberToIp(lastIP)}`,
netmask: numberToIp(mask),
wildcard: numberToIp(wildcard),
totalHosts,
usableHosts
};
};
計算例を見てみましょう。
入力: 192.168.1.100/24
IP: 192.168.1.100 = 0xC0A80164 = 11000000 10101000 00000001 01100100
Mask: 255.255.255.0 = 0xFFFFFF00 = 11111111 11111111 11111111 00000000
----------------------------------------------------------------------
Network: (IP & Mask) = 0xC0A80100 = 11000000 10101000 00000001 00000000
= 192.168.1.0
Wildcard: (~Mask) = 0x000000FF = 00000000 00000000 00000000 11111111
= 0.0.0.255
Broadcast: (Network | Wildcard)
= 0xC0A801FF = 11000000 10101000 00000001 11111111
= 192.168.1.255
サブネット分割の実装
ベースネットワークを複数のサブネットに分割する機能を実装します。
重要なポイントは「サブネット境界のアライメント」です。
const createSubnetWithPrefix = (subnetPrefix: number) => {
const subnetMask = (0xFFFFFFFF << (32 - subnetPrefix)) >>> 0;
let startIP = subnets.length > 0
? subnets[subnets.length - 1].endIP + 1
: baseNetwork;
// サブネット境界にアライメント
const subnetNetwork = startIP & subnetMask;
const alignedStart = subnetNetwork < startIP
? (subnetNetwork + (1 << (32 - subnetPrefix))) >>> 0
: subnetNetwork;
const subnetWildcard = ~subnetMask >>> 0;
const subnetBroadcast = alignedStart | subnetWildcard;
if (subnetBroadcast > baseBroadcast) {
alert('範囲を超えています');
return;
}
const newSubnet: Subnet = {
id: nextSubnetId,
name: `サブネット${nextSubnetId}`,
cidr: `${numberToIp(alignedStart)}/${subnetPrefix}`,
prefix: subnetPrefix,
startIP: alignedStart,
endIP: subnetBroadcast,
color: COLORS[(nextSubnetId - 1) % COLORS.length],
};
setSubnets([...subnets, newSubnet]);
setNextSubnetId(nextSubnetId + 1);
};
アライメントの具体例を見てみましょう。
ベースネットワーク: 192.168.1.0/24 (192.168.1.0 ~ 192.168.1.255)
サブネット1を/26で追加:
サブネット境界: 64の倍数 (2^(32-26) = 64)
開始IP: 192.168.1.0 (境界に一致)
終了IP: 192.168.1.63
→ 192.168.1.0/26
サブネット2を/26で追加:
前のサブネット終了IP + 1 = 192.168.1.64
サブネット境界: 64の倍数
192.168.1.64は境界に一致
終了IP: 192.168.1.127
→ 192.168.1.64/26
サブネット3を/27で追加:
前のサブネット終了IP + 1 = 192.168.1.128
サブネット境界: 32の倍数 (2^(32-27) = 32)
192.168.1.128は境界に一致
終了IP: 192.168.1.159
→ 192.168.1.128/27
もし境界に一致しない場合は、次の境界まで進めます。
サブネット1: 192.168.1.0/26 (~192.168.1.63)
サブネット2を/27で追加しようとする:
前のサブネット終了IP + 1 = 192.168.1.64
サブネット境界: 32の倍数
192.168.1.64 & 0xFFFFFFE0 = 192.168.1.64 (境界に一致)
→ 192.168.1.64/27 (~192.168.1.95)
大規模ネットワーク(例: /16で65,536個のIP)では、1セルに複数のIPを集約して表示します。
今後の拡張可能性
- IPv6対応(128ビット演算)
- VLSM(可変長サブネットマスク)の自動計算
- サブネット名の編集機能
- JSON/CSVエクスポート
- 重複チェック機能
まとめ
CIDR計算の本質は、ビット演算によるIPアドレスの操作です。サブネットマスク、ワイルドカードマスク、ネットワークアドレス、ブロードキャストアドレスは、すべて32ビット整数のAND/OR/NOT演算で求まります。
この実装を通じて、ネットワークの基礎とビット演算の実践的な使い方を学ぶことができました。
ツールを実際に試す:TechTools - CIDRチェッカー
Discussion