はじめに
Torinoa Tools に、CIDRブロックの分割を計算するサブネット計算ツールがあります。単純な「必要サブネット数で均等分割」だけでなく、**VLSM(Variable Length Subnet Masking/可変長サブネットマスク)**にも対応しています。
VLSMは「必要ホスト数がサブネットごとにバラバラ」なケースに対応するための計算方法で、例えば「本社100台・支店A 50台・支店B 25台・DMZ 10台」のように要件が異なる場合に、アドレス空間を無駄なく配分するために使います。この記事では、そのパッキングアルゴリズムの実装を紹介します。
何を計算する必要があるか
VLSMでは、各サブネットについて次を求める必要があります。
- 要求ホスト数を収容できる最小のプレフィックス長(サブネットマスク)
- 親ネットワークのアドレス空間内で、他のサブネットと重ならないように配置する開始アドレス
「1」は数式で一発で求まりますが、「2」は要求を大きい順に処理してアドレス空間に順番に詰めていく必要があります。
必要ホストビット数を求める
IPv4では、1サブネット内の全アドレス数から、ネットワークアドレスとブロードキャストアドレスの2つを引いた数が使用可能ホスト数になります。つまり 2^n - 2 >= 必要ホスト数 を満たす最小の n(ホストビット数)が必要です。
const hostBits = ceilLog2(hosts + 2);
const subnetPrefix = 32 - hostBits;
const subnetSize = Math.pow(2, hostBits);
hosts + 2 として ceilLog2 に渡すことで、「ネットワークアドレス・ブロードキャストアドレス分の2つ」を引く計算を、あらかじめ加算しておく形にしています。
大きい要求から詰める(貪欲法)
VLSMのアドレス配分では、サブネットサイズが大きいものから先に配置するのが定石です。小さいものから詰めると、後から大きいサブネットを境界に合わせて配置する際に隙間ができてしまい、アドレス空間を無駄にしてしまうためです。
function calculateVlsm(networkNum: number, prefix: number, parentTotal: number) {
const reqs = vlsmRequirements.value;
// Sort descending for efficient packing
const sorted = [...reqs].sort((a, b) => b - a);
let currentNum = networkNum;
const subnets: SubnetResult[] = [];
for (const hosts of sorted) {
const hostBits = ceilLog2(hosts + 2);
const subnetPrefix = 32 - hostBits;
const subnetSize = Math.pow(2, hostBits);
// Align to subnet boundary
const alignedNum = (Math.ceil(currentNum / subnetSize) * subnetSize) >>> 0;
if (alignedNum + subnetSize - 1 > networkNum + parentTotal - 1) {
// 親ネットワークに収まらない
errors.value.value = t("errVlsmOverflow");
results.value = [];
return;
}
const subnetMask = maskFromPrefix(subnetPrefix);
const broadcastNum = (alignedNum + subnetSize - 1) >>> 0;
subnets.push({
network: numToIp(alignedNum),
mask: numToIp(subnetMask),
broadcast: numToIp(broadcastNum),
firstHost: numToIp(alignedNum + 1),
lastHost: numToIp(broadcastNum - 1),
usableHosts: subnetSize - 2,
totalAddresses: subnetSize,
prefix: subnetPrefix,
});
currentNum = alignedNum + subnetSize;
}
results.value = subnets;
}
サブネット境界へのアライメント
CIDRのサブネットは、そのサイズの倍数の位置からしか開始できません(例えば512アドレスのサブネットは、512の倍数のアドレスからしか始められない)。この「次の境界」を求めているのが以下の行です。
const alignedNum = (Math.ceil(currentNum / subnetSize) * subnetSize) >>> 0;
currentNum(前のサブネットの直後のアドレス)を subnetSize で割って切り上げ、再度 subnetSize を掛けることで、「currentNum 以上で最も近いアライメント済みアドレス」を求めています。>>> 0 は符号なし32bit整数への変換で、IPv4アドレスをビット演算で扱う際の定番のイディオムです。
親ネットワークをはみ出したら明示的にエラーにする
if (alignedNum + subnetSize - 1 > networkNum + parentTotal - 1) {
errors.value.value = t("errVlsmOverflow");
results.value = [];
return;
}
境界アライメントによって隙間(ギャップ)が生まれるため、「要求ホスト数の合計が親ネットワークの総アドレス数以下」であっても、アライメントの都合で実際には収まらないケースがあります。ここを暗黙的に切り捨てたりせず、明示的にエラーとして返すようにしているのがポイントです。ユーザーは「要求が本当に収まるかどうか」をその場で確認できます。
おわりに
VLSMの計算自体は難しいアルゴリズムではありませんが、「大きい順にソートして詰める」「境界にアラインする」「はみ出しは即エラー」という3つを押さえておけば、正しく動くシンプルな実装になります。ネットワーク設計を手計算でやっていた人には、地味に便利なツールだと思います。
ツールはこちらから試せます: https://tools.torinoa.com/tools/subnet-calculator/