昨日の延長上の話。ある IP アドレスが、どのサブネットに属しているかがわかれば、あとはそのサブネットがどの国に割り当てられたものかがわかればいい。
cidr.txt
何はともあれ、IP アドレスの国別割り当て状況がわからないと話にならない。でも、世の中良くしたもんで、データを公開してくれている人がいた。ありがたい。
このページから、cidr.txt をダウンロードした。
CIDR ってのは Classless Inter-Domain Routing の略で、サブネットマスクを使って IP アドレスの割り当てとルーティングをする仕組みのこと。また、例えば 192.168.1.0/24 というサブネットの表記を CIDR表記というようだ。簡単な説明はここ。
cidr.txt は国名コードと割り当てられているサブネットの CIDR表記の一覧になっている。冒頭の10行はこうなっている。
AD 85.94.160.0/19 AD 91.187.64.0/19 AD 109.111.96.0/19 AD 185.4.52.0/22 AD 194.158.64.0/19 AE 2.48.0.0/14 AE 5.30.0.0/15 AE 5.32.0.0/17 AE 5.38.0.0/17 AE 5.53.96.0/21
国名コードと CIDR表記のサブネットはタブ文字で区切られている。
ちなみに AD はアンドラ、AE はアラブ首長国連邦だそうだ。
Ruby版
cidr.txt を読み込んでハッシュに保存しておき、入力された IP アドレスが属するサブネットを探して、見つかったらその国名コードを出力する。cidr.txt が実行ディレクトリにおいてあるのを前提としている。
# coding: utf-8
def main
  cidr = load_cidr("cidr.txt")
  addr = ARGV.shift
  cidr.each do |subnet, country|
    if in_subnet?(addr, subnet)
      puts country
      break
    end
  end
end
def load_cidr(file)
  cidr = {}
  File.open(file) do |f|
    f.each do |line|
      co, subnet = line.chomp.split("\t")
      cidr[subnet] = co
    end
  end
  cidr
end
def ip_to_b(addr)
  addr.split(".").map{|x| sprintf("%08d", x.to_i.to_s(2).to_i)}.join("")
end
def in_subnet?(addr, subnet)
  addr_b = ip_to_b(addr)
  subnet_addr, mask = subnet.split("/")
  subnet_addr_b = ip_to_b(subnet_addr)
  mask = mask.to_i
  addr_b.slice(0...mask) == subnet_addr_b.slice(0...mask)
end
main
IP アドレスの属するサブネットの検索が力技だけど、まあいいか。
実行結果。
takatoh@nightschool $ ruby -rresolv -e 'puts Resolv.getaddress("blog.panicblanket.com")'
210.224.185.170
takatoh@nightschool $ ruby addr_to_co.rb 210.224.185.170
JP
というわけで、このブログの IP アドレスは日本のものだってことが分かった。
Python版
同じやり方。
# coding: utf-8
import sys
def main():
    cidr = load_cidr("cidr.txt")
    addr = sys.argv[1]
    for subnet, co in cidr.items():
        if is_in_subnet(addr, subnet):
            print co
            break
def load_cidr(file):
    cidr = {}
    f = open(file)
    for line in f:
        co, subnet = line.rstrip().split("\t")
        cidr[subnet] = co
    return cidr
def ip_to_b(addr):
    return "".join(map(lambda x: "%08d" % int(bin(int(x))[2:]), addr.split(".")))
def is_in_subnet(addr, subnet):
    addr_b = ip_to_b(addr)
    subnet_addr, mask = subnet.split("/")
    subnet_addr_b = ip_to_b(subnet_addr)
    mask = int(mask)
    return addr_b[:mask] == subnet_addr_b[:mask]
main()
takatoh@nightschool $ python addr_to_co.py 210.224.185.170 JP