IPv6時代のセキュリティ対策
サーバ管理者であれば、何らかのアクセス規制を導入していると思います。
その中でも頼りになるのが国別のIPリストではないでしょうか?
国別のIPリストを利用すれば「SSHの接続は国内限定にする」「特定の国からのアクセスを一括で規制する」といった対応が可能になります。
当サイトでも以下の解説の際に利用しました。
iptablesの設定ファイルをシェルスクリプトを利用して動的に作成
iptablesをシェルスクリプトで設定していて動作が遅い場合の対処法
上記の解説で利用したリストは「世界の国別 IPv4 アドレス割り当てリスト」のものを利用させていただきました。
このリストの素晴らしいところはRIR(地域インターネットレジストリ)が公表しているIPリストを利用しやすいようにCIDR表記に変換してくれているところです。
非常にありがたいリストなのですが、残念ながらIPv6のリストは提供されていません。2017年現在IPv6の利用は、以下のグラフが示す通り、加速度的に増えています。
日本でも大手プロバイダやスマホでの利用で増加しており、10%を越えたといわれています。
Googleへのトラフィックが示す通り、世界中では2017年6月現在15%程度の機器がIPv6で接続されています。
IPv6のリストを公表しているサイトもあるのですが、個人サイトでした。今まで利用していたIPv4のリストを公表していたサイトもあくまで個人サイトです。
アクセス規制というサーバの根幹に関わるリストを、いつ停止するとも限らないソースに委ねるというのは、大きなリスクになります。
そこで、自前で「国別IPv4とIPv6のIPアドレス割当リストを作ろう」というのがページの趣旨です。
目次
- IPv4のIPアドレス割当リストを作る上での留意点
- ipv4_cidr_client_01.php
- ipv4_cidr_client_02.php
- ipv4_cidr_client_03.php
- ipv6_cidr_client_01.php
- ipv6_cidr_client_02.php
IPv4のIPアドレス割当リストを作る上での留意点
上でも解説しましたが、IPのリストはRIR(地域インターネットレジストリ)で公表されています。
具体的には以下のアドレスです。
ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest
ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
これらのリストには「世界の国別 IPv4 アドレス割り当てリスト」で解説されているとおり、いくつか問題があります。
フォーマットはRIR statistics exchange format – APNICで定義されており以下の書式で公開されています。
registry|cc|type|start|value|date|status[|extensions...]
サンプルで実例を示すと以下のようになっています。
afrinic|ZA|ipv4|169.159.128.0|16384|20160113|allocated|F3632FD4
必要なのはCCの部分で表現する国別コードです。
具体的には「ISO 3166-2」で定義された行政区画コードと呼ばれるものです。
続いてtypeの部分で「ipv4」か「ipv6」を識別します。
IPアドレスを示すのは開始アドレスである「start」と、IP数である「value」で表現されています。
問題点は以下のとおりです。
1.CIDR表記で割り切れない
valueの範囲をプレフィックスで表現するのですが、CIDR表記で割り切れる範囲とは限りません。
例えば範囲が「6144」となっている場合、「4096」と「2048」に分けて、「/20」と「/21」で表現する必要があります。
1.CIDR表記が細切れになっている
上のように分割しなければいけない項目がある反面、連続しているのに分割されている項目も存在します。ただでさえ長いリストなので、結合できる部分は結合します。
1.順序がバラバラ
リストを利用する際はプログラムで走査するので、そのままでも問題ありませんが、視覚的に把握しやすくするためソートします。
使い勝手の良いリストを作成するには、これらの問題を解決する必要があります。
サーバ用ということでBashでも良かったのですが、さくっと作りたかったので使い慣れたPHPで作成しました。
ipv4_cidr_client_01.php
まずはIPv4の国別リストを読み込みます。
RIRからリストを取得し、後処理のためにIPアドレスを長整数表現に変換し、ファイルに書き出します。
いくつかのファイルに分けるのは原因の切り分けが容易になるのと、メモリ消費を抑えるためです。
ファイル名や作成場所は環境に合わせて変えてください。
今回は「/usr/script」というディレクトリを作成してファイルを作成しました。
# mkdir /usr/script # cd /usr/script # vi ipv4_cidr_client_01.php
ソースを見ていただければわかると思いますが、作成されるリストや、製作途中のリストは/tmpディレクトリに保存されます。
ちなにみtmpディレクトリはデフォルトでファイルの作成から240時間経過すると削除されるためリスト作成の頻度には注意してください。
<?php /* * 1.IPのリストを取得 * * 2.後処理のためにIPアドレスを長整数表現に変換し * 国別コードと合わせて書き出す * */ define('TEMP_PATH', '/tmp'); define('CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr_01.txt'); // リストのダウンロード passthru( 'wget -qO ' . TEMP_PATH . '/delegated-arin-extended-latest ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest' ); passthru( 'wget -qO ' . TEMP_PATH . '/delegated-ripencc-extended-latest ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest' ); passthru( 'wget -qO ' . TEMP_PATH . '/delegated-apnic-extended-latest ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest' ); passthru( 'wget -qO ' . TEMP_PATH . '/delegated-lacnic-extended-latest ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest' ); passthru( 'wget -qO ' . TEMP_PATH . '/delegated-afrinic-extended-latest ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest' ); $wfp = fopen( CIDR_LIST_PATH, 'w' ); // ダウンロードしたファイルを全て回す foreach ( glob( TEMP_PATH . '/delegated-*-extended-latest' ) as $filename ) { // ファイルを読み込み空行と行末の改行を飛ばす $lists = new SplFileObject( $filename ); $lists->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); foreach ( $lists as $line ) { if ( preg_match( '/(arin|ripencc|apnic|lacnic|afrinic)\|[A-Z]+\|ipv4/', $line ) ) { $rows = explode('|', $line); $country = $rows[1]; // 国別コード取得 $ipMin = ip2long( $rows[3] ); // IPの一番下を指定。後に使う関数用にip2longでIPアドレスを整数型へ変換 $ipMax = $ipMin + $rows[4] - 1; // IPの一番上を指定。上のIPに範囲を足して1引いたもの fwrite( $wfp, $country . "\t" . $ipMin . "\t" . $ipMax . "\n" ); } } } fclose( $wfp );
見たままで特に解説するところもありませんが、メモリを節約するためファイルの読み込みにはSplFileObjectを利用しています。
setFlagsで空の行や最後の改行などを無視します。
ちなみにオブジェクトはそのままforeachで回せます。
この方法であればfopen()で開いてfgets()で読み込みfclose()で閉じるといった手間もかかりません。
またfile_get_contents()のように巨大なファイルを丸々メモリに載せる必要もありません。
ipv4_cidr_client_02.php
続いて国名とIPでソートします。
さらにサブネット部をCIDR表記で表現できる一番大きな値に分割します。
CIDR表記で割り切れるIPアドレス数は以下のサイトを参照してください。
サブネットマスク計算(IPv4)/サブネット一覧(早見表)
<?php /** * 1.リストをソートし国とIPが連続する場合結合 * * 2.IPがCIDR形式(サブネットマスク形式)で割り切れない場合、 * CIDR形式で表現できるように分割するスクリプト * */ define( 'TEMP_PATH', '/tmp' ); define( 'OLD_CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr_01.txt' ); define( 'CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr_02.txt' ); // 書き出すIPリスト // IPリストをソートしてキーを振り直す $lists = new SplFileObject(OLD_CIDR_LIST_PATH); $lists->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); $arr = array(); foreach( $lists as $val ){ if ($val === false) continue; $arr[] = $val; } asort($arr); $arr = array_merge($arr); // 国の名前が同じで、現在のip_maxと次のip_minが連続している場合結合する $i = 0; foreach ( $arr as $key => &$val ) { if ( isset( $arr[$i + 1] ) ){ $j = explode( "\t", $val); $k = explode( "\t", $arr[$i + 1]); if ( $j[0] == $k[0] && $j[2] == $k[1] - 1 ){ $arr[ $i + 1 ] = $j[0] . "\t" . $j[1] . "\t" . $k[2]; unset( $arr[$i] ); } } $i++; } unset($val); // 参照渡しのリセット // $cidr_rangesにCIDR形式で使われる4から2147483648の倍数をセット // 参照:https://note.cman.jp/network/subnetmask.cgi $range = array(); for( $i = 0; $i < 30; $i++ ){ $ranges[] = pow( 2, $i + 2 ); } $cidr_ranges = array_reverse( $ranges ); /** * IPの範囲を調べる * ip_range_cidr_splitへIPの範囲を渡してCIDR形式に分割し * 新たなファイルに書き出す */ $wfp = fopen( CIDR_LIST_PATH, 'w' ); foreach( $arr as $key => $val ){ // IPのrangeの取得 if( ! empty( $val ) ){ $j = explode( "\t", $val ); $country = $j[0]; $ip_min = $j[1]; $ip_max = $j[2]; $ip_range = $ip_max - $ip_min + 1; $split_lists = ip_range_cidr_split( $ip_range, $cidr_ranges ); foreach ( $split_lists as $row ) { $ip_max = $ip_min + $row - 1; fwrite( $wfp, $country . "\t" . $ip_min . "\t" . $ip_max . "\n" ); $ip_min = $ip_min + $row; } } } fclose( $wfp ); /** * IPの範囲とCIDR表記で割り切れる値を渡すと * CIDRで表現できるIPの範囲を配列で返す * * @param int IPの範囲 * @param arr CIDR表記で割り切れる値 * @return arr CIDRで表現できるIPの範囲を配列で返す */ function ip_range_cidr_split( $ip_range, $cidr_ranges ) { $split_lists = array(); foreach( $cidr_ranges as $cidr_range ){ // $rangeで引いて、残りをもう一度この関数で処理(割り切れるまで実行) if( $ip_range == $cidr_range ){ // 割り切れる場合は処理終了 $split_lists[] = $ip_range; return $split_lists; } elseif ( $ip_range > $cidr_range ){ $split_lists[] = $cidr_range; $ip_range = $ip_range - $cidr_range; } } return $split_lists; }
割り切れる値に分割する理由は次の項目で解説します。
ipv4_cidr_client_03.php
下準備が終わったのでIPをCIDR表記に変換します。
関数PlageVersCIDRs()について
変換するためのスクリプトは以下のサイトのものを利用させていただきました。
http://php.net/manual/ja/ref.network.php#75922
ざっくり解説するとIPの開始(min)と終わり(max)の値を2進数に直し、minの後ろをカウンターを利用して1に置換し、maxの値になるまで比較。最終的なカウンターの値でプレフィックスを取得。
最後にlong2ipとbindecでデコードという流れです。
どうやったらこんなコードを閃くのか頭を覗いてみたいですねw
便利な関数なのですが、一つ問題点があります。比較の際にCIDR表記で割り切れる値で丸めてしまうため、端数を捨ててしまうという点です。
そのためにipv4_cidr_client_02.phpで割り切れる値に分割したというわけです。(他にも解決方法はたくさんありますが、一番最初に思いついた方法を採用しましたw)
ファイル作成の成否をメールで通知
リストを作成するにあたり、RIRのIPリストをWeb上から取得する以上、失敗することもあります。
そこで前回作成したリストと比較して、相違点が多い場合はリストを破棄し、メールで通知します。
<?php /** * 1.IPをCIDR表記に変換する * * 2.リストの差分がしきい値を超えた場合はリストを破棄 * * 3.リスト作成の成否をメールで通知する * */ define( 'TEMP_PATH', '/tmp' ); define( 'OLD_CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr_02.txt' ); define( 'TMP_CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr_03.txt' ); define( 'CIDR_LIST_PATH', TEMP_PATH . '/ipv4_cidr.txt' ); $lists = new SplFileObject(OLD_CIDR_LIST_PATH); $lists->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); /** * IPをCIDR形式に変換するスクリプトに渡して * ファイルへ書き出す */ $wfp = fopen( TMP_CIDR_LIST_PATH, 'w' ); foreach( $lists as $list ){ // IPのrangeの取得 $j = explode( "\t", $list ); $country = $j[0]; $ip_min = $j[1]; $ip_max = $j[2]; $split_lists = PlageVersCIDRs( $ip_min, $ip_max ); foreach ( $split_lists as $row ) { fwrite( $wfp, $country . "\t" . $row . "\n" ); } } fclose( $wfp ); /** * 保存済みの /tmp/ipv4_cidr.txt にあって * 作成した /tmp/ipv4_cidr_03.txt にないものをカウント * * 変更点が多すぎる場合は * ファイルが正常に取得できていないものとみなし更新しない * 検査自体は1/2のみで、500の差分がある場合失敗(全体換算で1000) * * そしてメールで通知する */ if( is_readable( CIDR_LIST_PATH ) ) { $new_file = new SplFileObject( TMP_CIDR_LIST_PATH ); $new_file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); $old_file = new SplFileObject( CIDR_LIST_PATH ); $old_file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); // 保存されたファイルの1/2の行数を取得 $old_file->seek( $old_file->getSize() ); $lines_total = $old_file->key(); $lines_total = $lines_total / 2; $i = 0; foreach( $new_file as $val ){ if( $lines_total < $i ){ break; } $new_data[] = $val; $i++; } $i = 0; foreach( $old_file as $val ){ if( $lines_total < $i ){ break; } $old_data[] = $val; $i++; } $diff = count( array_diff( $new_data, $old_data ) ); $diff2 = count( array_diff( $old_data, $new_data ) ); // 差分が500以下のときに成功、それ以上のときに失敗コピーしない if ( $diff < 500 && $diff2 < 500 ){ send_mail( "成功", $diff + $diff2 ); copy( TMP_CIDR_LIST_PATH, CIDR_LIST_PATH ); } else { send_mail( "失敗", $diff + $diff2 ); } } else { send_mail( "新規作成成功", 0 ); copy( TMP_CIDR_LIST_PATH, CIDR_LIST_PATH ); } /** * メール送信用 * 成否と差分を記載したメールを送信する * * @param string 成功もしくは失敗 * @param int 差分の数値 */ function send_mail( $flag, $diff ){ mb_language("Japanese"); mb_internal_encoding("UTF-8"); if ( mb_send_mail( "hoge@example.com", "IPv4のCIDRリスト作成に「" . $flag . "」しました", "CIDRの作成:" . $flag . "。\nCIDRの差分:" . $diff . "。", "From: piyo@example.com" ) ){ } else { echo "メールの送信に失敗しました。"; } } /** * IPの開始と終了の範囲を渡すCIDR形式(サブネット形式)で返す * オリジナルのものはCIDRで表現できない端数の出る値を丸めていた * 参照:http://php.net/manual/ja/ref.network.php#75922 * * 一つ前の処理でCIDRで表現可能な数値に分割しているため * オリジナルのものはCIDRで表現できない端数の出る値を丸めていたが * 正確な値で分割が可能となっている * * @param int IPの開始点 * @param arr IPの終了点 * @return arr CIDR形式で返す */ function PlageVersCIDRs($ip_min, $ip_max) { $cidrs = array(); $ip_min_bin = sprintf('%032b', $ip_min); $ip_max_bin = sprintf('%032b', $ip_max); $ip_cour_bin = $ip_min_bin; while (strcmp($ip_cour_bin, $ip_max_bin) <= 0) { $lng_reseau = 32; $ip_reseau_bin = $ip_cour_bin; while (($ip_cour_bin[$lng_reseau - 1] == '0') && (strcmp(substr_replace($ip_reseau_bin, '1', $lng_reseau - 1, 1), $ip_max_bin) <= 0)) { $ip_reseau_bin[$lng_reseau - 1] = '1'; $lng_reseau--; } $cidrs[] = long2ip(bindec($ip_cour_bin)).'/'.$lng_reseau; $ip_cour_bin = sprintf('%032b', bindec($ip_reseau_bin) + 1); } return $cidrs; }
これで「tmp/ipv4_cidr.txt」というIPv4の国別リストが作成できました。
mb_send_mail()のhoge@example.comとpiyo@example.comの部分は環境に合わせて変更してください。
ファイル作成の成否を決める差分のしきい値ですが、全体の1/2が500行異なる場合に失敗と判断しています。(全体からすると1000行)
この部分は実際に運用する際に非常に大切な部分です。リストの取得はほとんど失敗しませんが、100回に1度程度の割合で失敗することがあります。中途半端なリストを元にiptables等でアクセス制御すると大事故に繋がります。例えばコンソールでの接続は日本のIP限定としていた場合、リストが正常に取得できていない場合、リモートから接続できなくなります。
更新の頻度ですが、実際に運用してみると、1日に更新される数は100以下が多いようです。ただし、まれに大漁のリストが更新されることがあるので、何度もエラーのメールが届く場合は手動で更新するか、しきい値を見直すなど、調整が必要になります。
リストの半分で判断しているのはメモリの節約のためです。
比較対象のファイルを2つとも配列に入れると200MBを超えたため苦肉の策です。正確性を重視したい場合は全て取得して比較するのが良いと思います。
今考えるとBashのdiffを使って<をカウントすれば簡単でしたね。(もしくはMySQLを利用する)
ipv6_cidr_client_01.php
IPv6のリストはCIDR表記で割り切れる値で割り振られているので、そのまま書き出すだけです。
先程取得したRIRのリストを利用して加工します。
<?php /* * IPv6のCIDR形式の割当リストを作成する * IPv4で取得したリストを利用する * */ define('TEMP_PATH', '/tmp'); define('CIDR_FILTER_PATH', TEMP_PATH . '/ipv6_cidr_01.txt'); $wfp = fopen( CIDR_FILTER_PATH, 'w' ); // ダウンロードしたファイルを全て回す foreach( glob( TEMP_PATH.'/delegated-*-extended-latest' ) as $filename ) { $lists = new SplFileObject( $filename ); $lists->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); foreach ( $lists as $line ) { if( preg_match( '/(arin|ripencc|apnic|lacnic|afrinic)\|[A-Z]+\|ipv6/', $line ) ) { $rows = explode( '|', $line ); $country = $rows[1]; // 国別コード取得 $ip = $rows[3]; // IPの一番下を指定。後に使う関数用にip2longでIPアドレスを整数型へ変換 $mask = $rows[4]; // IPの一番上を指定。上のIPに範囲を足して1引いたもの fwrite( $wfp, $country . "\t" . $ip . "/" . $mask ."\n" ); } } } fclose($wfp);
ipv6_cidr_client_02.php
後はソートして書き出すだけです。
IPv4と同じようにファイル作成の成否をメールで通知します。こちらはリストの行数が少ないため全て変数に入れて比較しています。
<?php /* * 1.リストをソートし使える形式で書き出す * * var 1.0.0 2017/5/11 */ define( 'TEMP_PATH', '/tmp' ); define( 'OLD_CIDR_LIST_PATH', TEMP_PATH . '/ipv6_cidr_01.txt' ); define( 'TMP_CIDR_LIST_PATH', TEMP_PATH . '/ipv6_cidr_02.txt' ); define( 'CIDR_LIST_PATH', TEMP_PATH . '/ipv6_cidr.txt' ); // 書き出すIPリスト // IPリストをソートしてキーを振り直す $lists = new SplFileObject(OLD_CIDR_LIST_PATH); $lists->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); foreach( $lists as $val ){ if ($val === false) continue; $arr[] = $val; } asort($arr); $wfp = fopen( TMP_CIDR_LIST_PATH, 'w' ); foreach( $arr as $line ){ fwrite( $wfp, $line ."\n" ); } fclose( $wfp ); /** * 保存済みの /tmp/ipv6_cidr.txt にあって * 作成した /tmp/ipv6_cidr_02.txt の差分を調べる * * 変更点が多すぎる場合は * ファイルが正常に取得できていないものとみなし更新しない * * そしてメールで通知する */ if( is_readable( CIDR_LIST_PATH ) ) { $new_file = new SplFileObject( TMP_CIDR_LIST_PATH ); $new_file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); $old_file = new SplFileObject( CIDR_LIST_PATH ); $old_file->setFlags(SplFileObject::READ_AHEAD | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE); foreach( $new_file as $val ){ $new_data[] = $val; } foreach( $old_file as $val ){ $old_data[] = $val; } $diff = count( array_diff( $new_data, $old_data ) ); $diff2 = count( array_diff( $old_data, $new_data ) ); // 差分が1000以下のときに成功、それ以上のときに失敗コピーしない if ( $diff < 1000 && $diff2 < 1000 ){ send_mail( "成功", $diff + $diff2 ); copy( TMP_CIDR_LIST_PATH, CIDR_LIST_PATH ); } else { send_mail( "失敗", $diff + $diff2 ); } } else { send_mail( "新規作成成功", 0 ); copy( TMP_CIDR_LIST_PATH, CIDR_LIST_PATH ); } /** * メール送信用 * 成否と差分を記載したメールを送信する * * @param string 成功もしくは失敗 * @param int 差分の数値 */ function send_mail( $flag, $diff ){ mb_language("Japanese"); mb_internal_encoding("UTF-8"); if ( mb_send_mail( "hoge@example.com", "IPv6のCIDRリスト作成に「" . $flag . "」しました", "CIDRの作成:" . $flag . "。\nCIDRの差分:" . $diff . "。", "From: piyo@example.com" ) ){ } else { echo "メールの送信に失敗しました。"; } }
こちらもメールの部分は環境に合わせて変更してください。
あとはcronで適当な頻度で実行するだけです。
個人的には一日に一度取得すれば十分だと思います。
以上で/tmpディレクトリに「ipv4_cidr.txt」と「ipv6_cidr.txt」が作成されます。
ipv4_cidr.txtのサンプル
AD 85.94.160.0/19 AD 91.187.64.0/19 AD 109.111.96.0/19 AD 185.4.52.0/22 こんなのがずら~っと13万行ほど
ipv6_cidr.txtのサンプル
AD 2a02:8060::/31 AD 2a02:c690::/31 AD 2a03:7ac0::/32 AE 2001:67c:2040::/48 こちらは3万6千行ほど
こうして最新のリストを自前で作成しておけば、サーバが停止しない限り最新のIPリストでアクセスを規制することができます。
あれ768とか1792は予約済みで配布はしてないやつですよね?
気になって半端分を調べてみましたが全部reservedか*でした。
例えば
168.151.29.0|768から無理やり出そうとすると
512は168.151.28/23で256は168.151.29.0/24でアドレスがかぶりませんか??
そのようなルールがあるのですね。勉強になります。
いまではIPv6のリストも公開されているので、結局はそちらを利用していますw
以前はリストから自作が前提でしたが、現在はipv6の国別リストを作って配布しているサイトがあるようです。
自分で作れるに越したことはないですが、ipv4と同じような書き方で配布している方がいらっしゃるので、そちらをipv4のものと同じように処理して使った方が楽かもしれません。
コメントありがとうございます。
たしかに最近はipv6のリストを配布しているサイトも増えたみたいですね。
問題は、仕事での使用に耐えるかなんですよね…。
公的な機関が配布してれると助かるんですが…。
自前で、日本に割り当てられたアドレスを抽出するプログラムを作成する必要があり、
ロジックについて、大変参考人させて頂きました。ありがとうございます。
ただ、作成している中で、記載のソースの流れそのままだと問題があることがわかったため、
情報共有をさせて頂きます。(ソースのご提供ができなくて申し訳ありません)
いずれも、「1つにまとめられるべきアドレス帯が分かれて出力されるだけ」であり、
現在のプログラムの結果を元にアドレス制限をして、不具合が出るものではありません。
ただ、「連続しているアドレス帯はまとめて出力する」ことも、本プログラムの目的の一つと思われるため、
ご指摘をさせて頂きます。
1.
STEP1で整数化してファイル出力しているが、STEP2で出力結果を元に、文字列評価でasortすることを想定していない。
整数部分の桁数を合わせないと正しいソートができないため、sprintfで10桁の0パディングをして、出力するべきかと思います
2.
関数ip_range_cidr_split のロジックが、「IPアドレスのブロック長について、2の倍数の大きい順に分割」
というロジックになっていますが、これは正しくありません。
例えば、
192.168.1.128/25 と 192.168.2.0/24 にまたがったアドレスの場合、今のロジックだと
192.168.1.128/24 と 192.168.2.128/25 に分割されてしまいます。
(その後STEP3により、192.168.1.128/25 192.168.2.0/25 192.168.2.128/25 の3つに分割されます)
そのため、
・引数に開始IPアドレス(ip_min)を追加
・2の倍数で、cidr_rangeを求めたあと「ip_minを2進数化(32文字の前ゼロパディング)して、ネットマスク部分に1が含まれていないときのみ」該当のブロックを配列に入れる
(foreach内に、カウントアップする変数 i を追加した上で、strpos(二進数化したip_min ,’1′ ,i+1) で判断しました)
→その後、ip_min = ip_min+cidr_range、ip_range=ip_range-cidr_range して、cidr_rangesのforeachを最初からまわし直す
という形で実装するロジックで関数を置き換えました。
あと、これは重箱の隅になっちゃいますが、
ipv4_cidr_client_02の61行目の
$range = array(); は
$ranges = array(); ではないかと思います。
以上、ご指摘をさせて頂きます。
よろしくお願いします。
詳しいご指摘ありがとうございます。
時間が取れたら内容を精査してコードに反映させていただきます。