Skip to content

Instantly share code, notes, and snippets.

@kubo39
Created October 20, 2011 07:14
Show Gist options
  • Save kubo39/1300597 to your computer and use it in GitHub Desktop.
Save kubo39/1300597 to your computer and use it in GitHub Desktop.
# -*- 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