Created
December 16, 2009 05:53
-
-
Save libc/257629 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
root = File.join(File.dirname(__FILE__), %w{ lib }) | |
if File.exist?(File.join(root, "eventmachine.rb")) | |
$:.unshift(root) | |
else | |
require 'rubygems' | |
end | |
# em-dns | |
require 'eventmachine' | |
require 'resolv' | |
module EventMachine | |
module DnsResolver | |
## | |
# Global interface | |
## | |
def self.resolve(hostname) | |
Request.new(socket, hostname) | |
end | |
def self.socket | |
unless defined?(@socket) | |
@socket = DnsSocket.open | |
end | |
@socket | |
end | |
def self.nameserver=(ns) | |
@nameserver = ns | |
end | |
def self.nameserver | |
unless defined?(@nameserver) | |
IO::readlines('/etc/resolv.conf').each do |line| | |
if line =~ /^nameserver (.+)$/ | |
@nameserver = $1.split(/\s+/).first | |
end | |
end | |
end | |
@nameserver | |
end | |
## | |
# Socket stuff | |
## | |
class RequestIdAlreadyUsed < RuntimeError | |
end | |
class DnsSocket < EM::Connection | |
def self.open | |
EM::open_datagram_socket('0.0.0.0', 0, self) | |
end | |
def post_init | |
@requests = {} | |
EM.add_periodic_timer(0.1, &method(:tick)) | |
end | |
# Periodically called each second to fire request retries | |
def tick | |
@requests.each do |id,req| | |
req.tick | |
end | |
end | |
def register_request(id, req) | |
if @requests.has_key?(id) | |
raise RequestIdAlreadyUsed | |
else | |
@requests[id] = req | |
end | |
end | |
def send_packet(pkt) | |
send_datagram(pkt, nameserver, 53) | |
end | |
def nameserver=(ns) | |
@nameserver = ns | |
end | |
def nameserver | |
@nameserver ||= DnsResolver.nameserver | |
end | |
# Decodes the packet, looks for the request and passes the | |
# response over to the requester | |
def receive_data(data) | |
msg = nil | |
begin | |
msg = Resolv::DNS::Message.decode data | |
rescue | |
else | |
req = @requests[msg.id] | |
if req | |
@requests.delete(msg.id) | |
req.receive_answer(msg) | |
end | |
end | |
end | |
end | |
## | |
# Request | |
## | |
class Request | |
include Deferrable | |
attr_accessor :retry_interval | |
attr_accessor :max_tries | |
def initialize(socket, hostname) | |
@socket = socket | |
@hostname = hostname | |
@tries = 0 | |
@last_send = Time.at(0) | |
@retry_interval = 3 | |
@max_tries = 5 | |
EM.next_tick { tick } | |
end | |
def tick | |
# Break early if nothing to do | |
return if @last_send + @retry_interval > Time.now | |
if @tries < @max_tries | |
send | |
else | |
fail 'retries exceeded' | |
end | |
end | |
# Called by DnsSocket#receive_data | |
def receive_answer(msg) | |
addrs = [] | |
msg.each_answer do |name,ttl,data| | |
if data.kind_of?(Resolv::DNS::Resource::IN::A) || | |
data.kind_of?(Resolv::DNS::Resource::IN::AAAA) | |
addrs << data.address.to_s | |
end | |
end | |
if addrs.empty? | |
fail "rcode=#{msg.rcode}" | |
else | |
succeed addrs | |
end | |
end | |
private | |
def send | |
@socket.send_packet(packet.encode) | |
@tries += 1 | |
@last_send = Time.now | |
end | |
def id | |
begin | |
@id = rand(65535) | |
@socket.register_request(@id, self) | |
rescue RequestIdAlreadyUsed | |
retry | |
end unless defined?(@id) | |
@id | |
end | |
def packet | |
msg = Resolv::DNS::Message.new | |
msg.id = id | |
msg.rd = 1 | |
msg.add_question @hostname, Resolv::DNS::Resource::IN::A | |
msg | |
end | |
end | |
end | |
end | |
def resolve_domain(domain) | |
df = EM::DnsResolver.resolve(domain) | |
df.timeout 180 | |
df.callback do |ips| | |
puts "#{domain} has #{ips.inspect}" | |
yield | |
end | |
df.errback do |*err| | |
puts "#{domain} hasn't been resolved #{err.inspect}" | |
yield | |
end | |
end | |
DOMAINS = (1..10000).map { |i| "domain#{i}.com" } | |
def schedule | |
d = DOMAINS.shift | |
return unless d | |
resolve_domain(d) { schedule } | |
end | |
EM.set_max_timers(100_000) | |
if !EM.kqueue? && !EM.epoll? | |
puts "You have to have either epoll or kqueue" | |
exit | |
end | |
EM.kqueue = true if EM.kqueue? | |
EM.epoll = true if EM.epoll? | |
EM.run do | |
16.times { schedule } | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment