こんにちは、幅広い視野を持つエンジニアを目指しています田中と申します。
今日はネットワークアドレスの扱い方を見ていきます。
目的
特定のIPアドレスが、特定のネットワークの範囲内にあるかどうか調べたい。また、サブネットマスクやブロードキャストアドレスなどの算出や検証も併せて行いたい、何か良い方法はないか調べる
やってみた事
IPアドレスを整数(32bit unsigned int)0 ~ 4,294,967,295)に変換して値の範囲を調べる
きっかけ
疎通確認などでよく使うpingコマンドですが、
ping [IPアドレス]
のように使いますが、私はある日誤って以下のように入力してしまいました。
ping [正の整数]
結果は以下のようになります。
C:\Users\tanaka>ping 8.8.8.8 8.8.8.8 に ping を送信しています 32 バイトのデータ: 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 の ping 統計: パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 3ms、最大 = 3ms、平均 = 3ms C:\Users\tanaka>ping 123456789 7.91.205.21 に ping を送信しています 32 バイトのデータ: Ctrl+C ^C
10進数ドット区切りのIPアドレス以外をパラメータとして与えてもエラーにはなりませんでした。上記はWindowsのpingコマンドの結果ですが、Linuxなどのpingコマンドでも同様の結果となります。
以上のことから、pingコマンドは32bitのIPアドレスを10進数として解釈して処理しているのではないかと考え、自分のプログラムやスクリプトにも応用できるのではないかと考えました。
IPアドレスを10進数32bit unsigned intに変換してpingに与えてみた
それでは、Google Public DNSの”8.8.8.8″を10進数の32ビットの符号なし整数に変えてpingにパラメータとして与えてみます。
10進数のint型0を32bitで表すと以下のようになります。
0000 0000 0000 0000 0000 0000 0000 0000
10進数のint型8を2進数で表すと1000なので”8.8.8.8″は以下であると考えました。
0000 1000 0000 1000 0000 1000 0000 1000
これを10進数に直します。
2^27 + 2^19 + 2^11 + 2^3
= 134217728 + 524288 + 2048 + 8
= 134744072
これをpingのパラメータに与えてみます。
C:\Users\tanaka>ping 134744072 8.8.8.8 に ping を送信しています 32 バイトのデータ: 8.8.8.8 からの応答: バイト数 =32 時間 =4ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 の ping 統計: パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 3ms、最大 = 4ms、平均 = 3ms
予想通りの結果となりました。
IPアドレスを10進数32bit unsigned intに変換して処理してみた
pingコマンドを真似てIPアドレスを32bit unsigned intとして扱い、TCP/IPネットワークの情報を算出してみます。
IPアドレスを32bit値に変換
IPアドレスの各オクテット(8bit)をint型の数字と見なし、4つ並べます。
0000 0000 0000 0000 0000 0000 0000 1000 // 8 0000 0000 0000 0000 0000 0000 0000 1000 // 8 0000 0000 0000 0000 0000 0000 0000 1000 // 8 0000 0000 0000 0000 0000 0000 0000 1000 // 8これを第一オクテットから順に、24ビット左シフト、第二オクテットを16ビット左シフト、第三オクテットを8ビット左シフトしてORをとります。
0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1000 ================================== [OR] 0000 1000 0000 1000 0000 1000 0000 1000 => 134744072
IPアドレスが正しいかどうか
10進数に変換したIPアドレスの範囲が0~4294967295の範囲に含まれるかどうか
CIDR(プレフィックス長)を32bitIPアドレスにする
2^((32 – プレフィックス長) – 1)を2進数にしたものと、0xFFFFFFFFでXORをとる
例: 24
0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 =================================== [XOR] 1111 1111 1111 1111 1111 1111 0000 0000 => 255.255.255.0
ネットワークアドレスを求める
2進数に変換したIPアドレスとプレフィックス長付きアドレス(CIDR)を2進数にしたもののANDをとる(ホスト部のビットをすべて0にする)
例: 192.168.0.1 , サブネットマスク255.255.255.0(192.168.0.1/24)
1100 0000 1010 1000 0000 0000 0000 0001 1111 1111 1111 1111 1111 1111 0000 0000 =================================== [AND] 1100 0000 1010 1000 0000 0000 0000 0000 => 192.168.0.0
ブロードキャストアドレスを求める
プレフィックス長付きアドレス(CIDR)をネットワークアドレスを論理否定(NOT)したものとXORをとる(ホスト部のビットをすべて1にする)
例: 192.168.0.1, サブネットマスク255.255.255.0(192.168.0.1/24)
1100 0000 1010 1000 0000 0000 0000 0000 =================================== [NOT] 0011 1111 0101 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 0000 0000 =================================== [XOR] 1100 0000 1010 1000 0000 0000 1111 1111 => 192.168.0.255bashなどNOT演算ができない場合は以下で代用する(プレフィックス長付きアドレス(CIDR)と0xFFFFFFFFでXORをとったものとIPアドレスでORをとる)
1111 1111 1111 1111 1111 1111 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 =================================== [XOR] 0000 0000 0000 0000 0000 0000 1111 1111 1100 0000 1010 1000 0000 0000 0000 0001 =================================== [OR] 1100 0000 1010 1000 0000 0000 1111 1111 => 192.168.0.255
連続したIPアドレスを求める
IPアドレスを32bit unsigned intにしてインクリメントするだけ、オクテット境界は考える必要なし
あるIP アドレスが特定のネットワークの範囲にあるか調べる
IPアドレスがネットワークアドレスとブロードキャストアドレスの間にあるか調べればよい
例: 192.168.0.200がデフォルトサブネットマスク255.255.255.0で192.168.0.1の範囲に所属するか
192.168.0.255 => 3232235775
192.168.0.1 => 3232235521
192.168.0.200 => 32322357203232235521 < 3232235720 < 3232235775 よって所属する
実際のコード例
#!/bin/bash # 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) )) } # 指定された個数だけ連続したIPアドレスの一覧を表示 function iplist(){ local num=$(ip2decimal $1) local max=$(($num + $2 - 1)) while : do decimal2ip $num [[ $num == $max ]] && break || num=$(($num+1)) done } # 特定の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 }
実行例
# IPアドレスを32bit値に変換 $ ip2decimal 192.168.0.1 3232235521 # 32bit値をIPアドレスに変換 $ decimal2ip 3232235521 192.168.0.1 # CIDR表記のネットワークアドレスを32bit値に変換 $ cidr2decimal 24 4294967040 # 特定のIPアドレスのネットワークアドレスを求める $ IP=192.168.0.1 $ CIDR=24 $ decimal2ip $(( $(ip2decimal $IP) & $(cidr2decimal $CIDR) )) 192.168.0.0 # 特定のIPアドレスのブロードキャストアドレスを求める $ MASK=255.255.255.0 $ decimal2ip $(( $(ip2decimal $IP) | (0xFFFFFFFF ^ $(ip2decimal $MASK)) )) 192.168.0.255 # 指定された数だけ特定のIPアドレスと連続したIPアドレスを求める $ 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 # 特定のIPアドレスが特定のネットワークに含まれるか調べる $ ipwith 192.168.0.1 255.255.255.0 192.168.0.200 $ echo $? 0 # 特定のIPアドレスが特定のネットワークに含まれるか調べる $ ipwith 192.168.0.1 $(decimal2ip $(cidr2decimal 24)) 192.168.1.200 $ echo $? 1
IPアドレスを10進数32bit unsigned intとしてIPアドレスの範囲を調べることができました!
pingのソースコードを読んでみた
Linuxのpingのソースコードが見つからなかったのでFreeBSDのソースコードを読んでみます。
https://svnweb.freebsd.org/base/head/sbin/ping/ping.c
553 target = argv[optind]; // コマンドラインパラメータに渡された値を読んでいる . . . /* to->sin_addrにtargetの値をnetwork byte order形式で入れる。パラメータの値がIPアドレスとして解釈できるかどうか */ 606 if (inet_aton(target, &to->sin_addr) != 0) { 607 hostname = target; // 解釈できる(32bit10進数値の場合もここを通る) 608 } . . . 841 (void)printf("PING %s (%s)", hostname, inet_ntoa(to->sin_addr)); // PING 134744072 (8.8.8.8)...と表示している
inet_ntoa()とinet_aton()が32bit数値とASCII文字列(10進数ドット区切りIPアドレス)の変換をしていそうです。
オンラインマニュアルのinet(3)にありました。
inet_aton() converts the Internet host address cp from the IPv4 numbers-and-dots notation into binary form (in network byte order) and stores it in the structure that inp points to. inet_aton() returns nonzero if the address is valid, zero if not. . . .
マニュアルによると16進数でも解釈できるとのことなのでやってみます。
C:\Users\tanaka>ping 0x08080808 8.8.8.8 に ping を送信しています 32 バイトのデータ: 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =3ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =4ms TTL=59 8.8.8.8 の ping 統計: パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 3ms、最大 = 4ms、平均 = 3ms C:\Users\tanaka>ping 0x08.0x08.0x08.0x08 8.8.8.8 に ping を送信しています 32 バイトのデータ: 8.8.8.8 からの応答: バイト数 =32 時間 =8ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =5ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =8ms TTL=59 8.8.8.8 からの応答: バイト数 =32 時間 =8ms TTL=59 8.8.8.8 の ping 統計: パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 5ms、最大 = 8ms、平均 = 7ms
16進数でもできました!
参考にさせていただいたサイト様(TCP/IP情報取得シェルスクリプトをそのまま使わせて頂いています)
http://qiita.com/harasou/items/5c14c335388f70e178f5
記事は以上になります。
このたびはご覧戴き、ありがとうございました。
投稿者プロフィール
最新の投稿
AWS2021.12.02AWS Graviton3 プロセッサを搭載した EC2 C7g インスタンスが発表されました。
セキュリティ2021.07.14ゼロデイ攻撃とは
セキュリティ2021.07.14マルウェアとは
WAF2021.07.13クロスサイトスクリプティングとは?