Created
October 20, 2011 07:14
-
-
Save kubo39/1300597 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
require 'socket' | |
class Resolver | |
@sock = Socket::new(Socket::AF_INET, Socket::SOCK_STREAM, 0) | |
@fd = @sock.fileno | |
# INETのSTRAMソケットを作成 | |
attr_reader :queue, :results, :dnsserver, :port | |
def initialize | |
@queue = [] | |
@results = {} | |
# GoogleのDNS使う | |
@dnsserver = '8.8.8.8' | |
@port = 53 | |
end | |
# 登録して溜まってたら処理 | |
def set fqdn | |
if @results.has_key?(fqdn) | |
qdata = query(fqdn) | |
sockaddr = Socket::sockaddr_in(@port, @dnsserver) | |
@sock.send(qdata, 0, sockaddr) | |
@results[fqdn] = nil | |
@queue << fqdn | |
sweep if @queue.size > 30 | |
end | |
end | |
# 溜まってるのを処理して質問に答える | |
def get fqdn, timeout=10 | |
sweep(timeout) if @queue | |
@results[fqdn] | |
end | |
# 溜まっているのを処理する | |
def sweep timeout=10 | |
start = Time.now.to_f | |
while [email protected]? | |
if IO::select(@sock, [], [], 0.001).first.size > 0 | |
# 受け取れるデータがあったら処理して結果にいれる | |
rdata = @sock.recvfrom(8192).first | |
# qrはresponseなら1 | |
qr = rdata[3].ord >> 7 | |
# rcodeは成功なら0 | |
rcode = rdata[4].ord & 31 | |
# responseで成功するならparse | |
if qr == 1 &&rcode == 0 | |
rfdqn, response = DNSResponse.new(rdata).parse | |
@queue.remove(rfqdn) | |
@results[rfqdn] = response | |
end | |
# 時間切れは終了 | |
break if (Time.now.to_f - start) > timeout | |
end | |
end | |
@queue.each {|q| @results.delete q } | |
# queueの初期化 | |
@queue = [] | |
end | |
# fqdnのIPアドレスを要求するクエリを作成 | |
def query fqdn | |
fqdn = fqdn.encode("UTF-8") | |
qid = [rand(2 ** 16 - 1)].pack("n") | |
header = qid + "\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00" | |
# d.hatena.ne.jp -> \x01d\x06hatena\x02ne\x02jp\x00 | |
qname = fqdn.split('.').map {|x| x.size.chr + x }.join + "\x00" | |
# 1 が "a host address" | |
qtype = [1].pack("n") | |
# 1 が "the Internet" | |
qclass = [1].pack("n") | |
header + qname + qtype + qclass | |
end | |
end | |
class DNSResponse | |
attr_reader :data | |
def initialize data | |
@data = data | |
@pos = 0 | |
end | |
# DNS responseをパースして二重配列で返す | |
def parse | |
results = [] | |
@pos = 12 | |
# response dataをみる | |
loop do | |
break if @data.size < @pos | |
rec_name = pick_name | |
rec_type = uint16 | |
rec_class = uint16 | |
rec_ttl = uint32 | |
rec_dlength = uint16 | |
if rec_type == 1 | |
# Aレコードの場合 | |
rec_data = parse_ipv4addr | |
elsif rec_type == 5 | |
# CNAMEレコードの場合 | |
rec_data = pick_name | |
elsif rec_type == 6 | |
# SOAレコードの場合 | |
name = pick_name | |
rname = pick_name | |
serial = uint32 | |
refresh = uint32 | |
expire = uint32 | |
minimum = uint32 | |
rec_data = [name, rname, serial, refresh, refresh, expire] | |
else | |
# それ以外だったらparseしないでbytesとして | |
rec_data = data[@pos..rec_dlength] | |
@pos += rec_dlength | |
result << [rec_name, rec_type, rec_class, rec_ttl, rec_data] | |
end | |
return qname, result | |
end | |
end | |
# @dataの@posバイト目から始まる名前を拾う | |
def pick_name | |
name_list = [] | |
loop do | |
length = uint8 | |
if length == 0 | |
break | |
elsif length >= 192 | |
name_pos = @data[(@pos-1)..(@pos+1)].unpack("n").first % (3 << 14) | |
pos, @pos = @pos, name_pos | |
name = pick_name | |
@pos = pos + 1 | |
name_list << name | |
break | |
else | |
name_list << @data[@pos..(@pos+length)] | |
@pos += length | |
end | |
name_list.join(".") | |
end | |
end | |
def uint8 | |
result = @data[@pos..(@pos+1)].unpack("C").first | |
@pos += 1 | |
result | |
end | |
def uint16 | |
result = @data[@pos..(@pos+2)].unpack("n").first | |
@pos += 2 | |
result | |
end | |
def unit32 | |
result = @data[@pos..(@pos+4)].unpack("N").first | |
@pos += 4 | |
result | |
end | |
def parse_ipv4addr | |
# IPaddressとしての返り値はString | |
result = @data[@pos..(@pos+4)].map {|x| x.ord.to_s }.join(".") | |
@pos += 4 | |
result | |
end | |
end | |
if __FILE__ == $0 | |
require 'open-uri' | |
resolver = Resolver.new | |
hatebu = open('http://b.hatena.ne.jp/') | |
fqdns = [] | |
hatebu.each do |line| | |
m = line.scan(/https?:\/\/([-a-zA-Z0-9\.]+)\//) | |
if m.any? | |
# p m.flatten | |
fqdns << m.flatten | |
end | |
end | |
# 重複したアドレスを削除 | |
fqdns.uniq! | |
puts fqdns.size | |
puts "start" | |
start = Time.now.to_f | |
fqdns.each do |fqdn| | |
resolver.set fqdn | |
end | |
fqdns.each do |fqdn| | |
resolver.get fqdn | |
end | |
puts Time.now.to_f - start | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment