会社で、ローカルIPアドレスを何千件とインクリメントして、テストに利用しているのを見て、IP の計算ってどうやるんだ?ってことで、試しにやってみた。
具体的に「IPの計算」ってのは、以下を想定
- ネットワークアドレスを求める
- ブロードキャストアドレスを求める
- 連続したIP の一覧を求める
- あるIP が特定のネットワークの範囲にあるか判定する
どうやって計算するか
人間が使う IPアドレスは見やすいように、オクテット単位に区切られて、10進で表記されている。こんな感じ。
192.168.0.1
でもこれでは計算しづらいので、実際の 32bit値に変更して計算するのが良さそう。
# 10進数
3232235521
# 2進数
11000000 10101000 00000000 00000001
前準備
32bitの値で計算する必要があるので、以下のような関数があると便利。
- IPアドレス表記 -> 32bit値 に変換 (ip2decimal)
- 32bit値 -> IPアドレス表記 に変換 (decimal2ip)
- CIDR 表記のネットワークアドレスを 32bit値に変換 (cidr2decimal)
3 については、ぱっと見、意味がわからないが、
192.168.0.0/24 の24
を 32bit値4294967040
(255.255.255.0)に変換するという意味。
で、作成した関数が、以下のようなもの。
# IPアドレス表記 -> 32bit値 に変換
function ip2decimal(){
local IFS=.
local c=($1)
printf "%s\n" $(( (${c[0]} << 24) | (${c[1]} << 16) | (${c[2]} << 8) | ${c[3]} ))
}
# 32bit値 -> IPアドレス表記 に変換
function decimal2ip(){
local n=$1
printf "%d.%d.%d.%d\n" $(($n >> 24)) $(( ($n >> 16) & 0xFF)) $(( ($n >> 8) & 0xFF)) $(($n & 0xFF))
}
# CIDR 表記のネットワークアドレスを 32bit値に変換
function cidr2decimal(){
printf "%s\n" $(( 0xFFFFFFFF ^ ((2 ** (32-$1))-1) ))
}
$ ip2decimal 192.168.0.1
3232235521
$ decimal2ip 3232235521
192.168.0.1
$ ip2decimal 255.255.255.0
4294967040
$ cidr2decimal 24
4294967040
やってることは、なんとなくわかると思いますが、一生懸命、頭にビット列の思い描きながら作りました。
計算
で、実際の計算。
1. ネットワークアドレスを求める
$ IP=192.168.0.1
$ CIDR=24
$
$ decimal2ip $(( $(ip2decimal $IP) & $(cidr2decimal $CIDR) ))
192.168.0.0
対象のIPアドレスを、ネットワークアドレスで AND(論理積&
)すればよい。そのまま。
2. ブロードキャストアドレスを求める
$ IP=192.168.0.1
$ MASK=255.255.255.0
$
$ decimal2ip $(( $(ip2decimal $IP) | (0xFFFFFFFF ^ $(ip2decimal $MASK)) ))
192.168.0.255
対象のIPアドレスを、ネットワークアドレスの論理否定(ビット反転)と OR(論理和|
)すればよい。(ただ、論理否定 ~
がうまく使えなかったので、XOR(排他的論理和)で代替)
3. 連続したIP の一覧を求める
function iplist(){
local num=$(ip2decimal $1)
local max=$(($num + $2 - 1))
while :
do
decimal2ip $num
[[ $num == $max ]] && break || num=$(($num+1))
done
}
$ iplist 192.168.0.253 5
192.168.0.253
192.168.0.254
192.168.0.255
192.168.1.0
192.168.1.1
32bit値にしてインクリメントしているので、オクテットの境界と考える必要がない。
4. あるIP が特定のネットワークの範囲にあるか
function ipwith(){
local addr=$1
local mask=$2
local num=$(ip2decimal $3)
local net=$(( $(ip2decimal $addr) & $(ip2decimal $mask) ))
local brd=$(( $(ip2decimal $addr) | (0xFFFFFFFF ^ $(ip2decimal $mask)) ))
[ $net -le $num -a $num -le $brd ] && return 0 || return 1
}
192.168.0.200
192.168.1.200
が特定範囲(192.168.0.1/255.255.255.0)にあるか
$ ipwith 192.168.0.1 255.255.255.0 192.168.0.200
$ echo $?
0
$ ipwith 192.168.0.1 255.255.255.0 192.168.1.200
$ echo $?
1
指定されたIPが、ネットワークアドレスとブロードキャストアドレスの間にあるかどうかで判定。
まとめ
まぁ、使うことなさそう。 たまに使ってます (2016/03 追記)。