🗃️

ビット演算で理解する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を集約して表示します。

今後の拡張可能性

  1. IPv6対応(128ビット演算)
  2. VLSM(可変長サブネットマスク)の自動計算
  3. サブネット名の編集機能
  4. JSON/CSVエクスポート
  5. 重複チェック機能

まとめ

CIDR計算の本質は、ビット演算によるIPアドレスの操作です。サブネットマスク、ワイルドカードマスク、ネットワークアドレス、ブロードキャストアドレスは、すべて32ビット整数のAND/OR/NOT演算で求まります。

この実装を通じて、ネットワークの基礎とビット演算の実践的な使い方を学ぶことができました。

ツールを実際に試す:TechTools - CIDRチェッカー

Discussion