Linux/Apache/国内アクセスへの限定

日本国内からのアクセスのみを許可するように設定する(2014-11-22)


海外からの掲示板荒らしを避けるために、
いっそ国内からのアクセスのみを受け付けるように変更してみようかと。

まず、APNIC*1からIPアドレス割り当て表を取得します。

$ wget http://ftp.apnic.net/stats/apnic/delegated-apnic-latest

中身はこんな感じ。IPアドレスの各国への割り当て状態が確認できます。

(抜粋)
apnic|AU|ipv4|1.0.4.0|1024|20110412|allocated
apnic|CN|ipv4|1.0.8.0|2048|20110412|allocated
apnic|JP|ipv4|1.0.16.0|4096|20110412|allocated

ちょっと何行あるか見てみましょうか。
日本(JP)のアドレスのみにgrepして行数をカウントしてみます。

$ grep JP delegated-apnic-latest  | grep ipv4 | wc -l
3266
$ grep JP delegated-apnic-latest  | egrep 'ipv4|ipv6' | wc -l
3738

おおぅ、フィルタにかけると3738行か。結構多いな。
次に IPv4、IPv6それぞれにサブネットマスクを付けてみます(IPフィルタルール用に)

$ grep JP delegated-apnic-latest  |
egrep 'ipv4|ipv6' |
awk -F'|'  '{print $3 "#" $4 "#" $5}' |
perl -e '
while(<STDIN>){
if(/ipv(.)#(.*)#(\d*)/){
   if($1 == 4){
       $ip=$2; $num=$3;
       do {
           if($ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/){
               $bin=$1*256*256*256
               +$2*256*256
               +$3*256
               +$4; # IP表記から数値に戻す
           }

           # ホストIPからネットマスクの数をカウント
           $bip=0;
           for($p=$bin; $p%2==0; $p>>=1){$bip++;}

           # 個数からマスクをカウント、小数点切り捨て
           $sub=sprintf("%d", log($num)/log(2));

           # ホスト、個数のどちらのマスクを使うか判定
           if($sub>$bip){ $sub=$bip; }

           printf("%s/%d\n", $ip, 32-$sub);

           $old = $num;
           $num = $num - (2 ** $sub);
           if($num > 0){
               if($ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/){
                  $b = $bin + $old - $num;
                  $ip = sprintf("%d.%d.%d.%d",
                     $b/(256*256*256),
                     $b/(256*256)%256,
                     $b/256%256,
                     $b%256
                  ); # 数値から、個数を足してIP表記に戻す
               }
           }
       } while($num > 0);

   }
   if($1 == 6){
       printf("%s/%d\n", $2, $num);
   }
}
}'
(で、エンター押して、実行結果が以下)
1.0.16.0/20
1.0.64.0/18
(中略)
240d::/20
240f::/20

英語苦手なんで、delegated-apnic-latestファイルの書式について、CIDRの解釈に自信が無いのですが・・・
http://www.apnic.net/publications/media-library/documents/resource-guidelines/rir-statistics-exchange-format
perlの部分(サブネットマスク)が正しいかどうか。
(追記:ダメでした。IPv4の場合はサブネットでは無く、個数なんですって。
 ホスト部も個数も2の乗数とは限らないから、なんか複雑に。)

上記コマンドの内容についてざっくり書くと、
egrepは、grepの正規表現対応でIPv4とv6の両方を拾ってます。
awkは、ipv4とかのバージョン、IPアドレス、ホスト数の3つを "#" 区切りで出力しています。
perl でホスト数からサブネットを拾おうとしています。
「log(ホスト数)/log(2)」や「>>=1」 で、ホスト数が2の何乗になるのかを求めて
サブネットマスクをかけたいのです。算数苦手・・・

ちょっと行数が多い気がするので、
日本国内だけじゃなくなってしまいますが、試しに条件を緩くして
IPv4のサブネットマスクを大雑把に/16ビットまで減らす。

grep JP delegated-apnic-latest  |
egrep 'ipv4|ipv6' |
awk -F'|'  '{print $3 "#" $4 "#" $5}' |
perl -e '
while(<STDIN>){
if(/ipv(.)#(.*)#(\d*)/){
    if($1 == 4){
        $subnet = 32 - log($3)/log(2);
        if($subnet < 17){
            printf("%s/%d\n", $2, $subnet);
        }else{
            if($2 =~ /^(\d+\D\d+)\D/){
                printf("%s.0.0/16\n", $1);
            }
        }
    }
    if($1 == 6){
        printf("%s/%d\n", $2, $num);
    }
}
}' | sort -u

それでも千行超えてしまいますけどね。
どこまでフィルタにかけるかは負荷を考えて後々更新してみるとして、
IPアドレスのAllowリストを作っちゃいます。 前述のコマンドの続きで、パイプ渡しして

 | awk '{printf "Allow from " $0 "\n"}'  > allow_list.txt
 echo "Allow from localhost" >> allow_list.txt

とかすれば良いでしょう。

では、httpd.confを編集

<Location />
    Order Allow,Deny
    (上記で作ったallow_list.txtを全部)
</Location>

掲示板だけ制限すれば良いんですが、いっそ全サイト(Location /)に制限かけてみました。
リストは長いですが、体感的にはアクセス速度は全然気にならない早さですね。
とりあえず、これで暫く様子を見ようと思います。


ただ、問題点もあるのは分かっています。

  1. どの周期でフィルタリスト(上記のAllowルール)を更新すればよいのか
  2. 国内からのアクセスには無力

1つめは、IPv4は既に枯渇しているから更新不要だと思うのですが、IPv6がどの程度普及して更新されるものなのか見当がつきません。
2つめは当然なんですが、この前ニュースで、某国が日本国内からのアクセスに偽装する為に東京の雑居ビルに踏み台サーバを用意していたなんてやってましたね。
日本国内に無料のプロキシサーバっていくつ位あるんでしょうか。
まぁ、これで一旦は様子見です。


*1 アジア地域のIPアドレスの割り当てを行っている機関。Asia Pacific Network Information Centre