eventmachine-le 1.1.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +21 -0
- data/.yardopts +7 -0
- data/GNU +281 -0
- data/LICENSE +60 -0
- data/README.md +80 -0
- data/Rakefile +19 -0
- data/eventmachine-le.gemspec +42 -0
- data/ext/binder.cpp +124 -0
- data/ext/binder.h +46 -0
- data/ext/cmain.cpp +841 -0
- data/ext/ed.cpp +1995 -0
- data/ext/ed.h +424 -0
- data/ext/em.cpp +2377 -0
- data/ext/em.h +243 -0
- data/ext/eventmachine.h +126 -0
- data/ext/extconf.rb +166 -0
- data/ext/fastfilereader/extconf.rb +94 -0
- data/ext/fastfilereader/mapper.cpp +214 -0
- data/ext/fastfilereader/mapper.h +59 -0
- data/ext/fastfilereader/rubymain.cpp +127 -0
- data/ext/kb.cpp +79 -0
- data/ext/page.cpp +107 -0
- data/ext/page.h +51 -0
- data/ext/pipe.cpp +347 -0
- data/ext/project.h +155 -0
- data/ext/rubymain.cpp +1269 -0
- data/ext/ssl.cpp +468 -0
- data/ext/ssl.h +94 -0
- data/lib/em/buftok.rb +110 -0
- data/lib/em/callback.rb +58 -0
- data/lib/em/channel.rb +64 -0
- data/lib/em/completion.rb +304 -0
- data/lib/em/connection.rb +728 -0
- data/lib/em/deferrable.rb +210 -0
- data/lib/em/deferrable/pool.rb +2 -0
- data/lib/em/file_watch.rb +73 -0
- data/lib/em/future.rb +61 -0
- data/lib/em/iterator.rb +313 -0
- data/lib/em/messages.rb +66 -0
- data/lib/em/pool.rb +151 -0
- data/lib/em/process_watch.rb +45 -0
- data/lib/em/processes.rb +123 -0
- data/lib/em/protocols.rb +37 -0
- data/lib/em/protocols/header_and_content.rb +138 -0
- data/lib/em/protocols/httpclient.rb +279 -0
- data/lib/em/protocols/httpclient2.rb +600 -0
- data/lib/em/protocols/line_and_text.rb +125 -0
- data/lib/em/protocols/line_protocol.rb +29 -0
- data/lib/em/protocols/linetext2.rb +161 -0
- data/lib/em/protocols/memcache.rb +331 -0
- data/lib/em/protocols/object_protocol.rb +46 -0
- data/lib/em/protocols/postgres3.rb +246 -0
- data/lib/em/protocols/saslauth.rb +175 -0
- data/lib/em/protocols/smtpclient.rb +365 -0
- data/lib/em/protocols/smtpserver.rb +663 -0
- data/lib/em/protocols/socks4.rb +66 -0
- data/lib/em/protocols/stomp.rb +202 -0
- data/lib/em/protocols/tcptest.rb +54 -0
- data/lib/em/queue.rb +71 -0
- data/lib/em/resolver.rb +195 -0
- data/lib/em/spawnable.rb +84 -0
- data/lib/em/streamer.rb +118 -0
- data/lib/em/threaded_resource.rb +90 -0
- data/lib/em/tick_loop.rb +85 -0
- data/lib/em/timers.rb +106 -0
- data/lib/em/version.rb +3 -0
- data/lib/eventmachine-le.rb +10 -0
- data/lib/eventmachine.rb +1548 -0
- data/rakelib/cpp.rake_example +77 -0
- data/rakelib/package.rake +98 -0
- data/rakelib/test.rake +8 -0
- data/tests/client.crt +31 -0
- data/tests/client.key +51 -0
- data/tests/em_test_helper.rb +143 -0
- data/tests/test_attach.rb +148 -0
- data/tests/test_basic.rb +294 -0
- data/tests/test_channel.rb +62 -0
- data/tests/test_completion.rb +177 -0
- data/tests/test_connection_count.rb +33 -0
- data/tests/test_defer.rb +18 -0
- data/tests/test_deferrable.rb +35 -0
- data/tests/test_epoll.rb +134 -0
- data/tests/test_error_handler.rb +38 -0
- data/tests/test_exc.rb +28 -0
- data/tests/test_file_watch.rb +65 -0
- data/tests/test_futures.rb +170 -0
- data/tests/test_get_sock_opt.rb +37 -0
- data/tests/test_handler_check.rb +35 -0
- data/tests/test_hc.rb +155 -0
- data/tests/test_httpclient.rb +190 -0
- data/tests/test_httpclient2.rb +128 -0
- data/tests/test_inactivity_timeout.rb +54 -0
- data/tests/test_ipv4.rb +125 -0
- data/tests/test_ipv6.rb +131 -0
- data/tests/test_iterator.rb +110 -0
- data/tests/test_kb.rb +34 -0
- data/tests/test_line_protocol.rb +33 -0
- data/tests/test_ltp.rb +138 -0
- data/tests/test_ltp2.rb +288 -0
- data/tests/test_next_tick.rb +104 -0
- data/tests/test_object_protocol.rb +36 -0
- data/tests/test_pause.rb +78 -0
- data/tests/test_pending_connect_timeout.rb +52 -0
- data/tests/test_pool.rb +196 -0
- data/tests/test_process_watch.rb +48 -0
- data/tests/test_processes.rb +133 -0
- data/tests/test_proxy_connection.rb +168 -0
- data/tests/test_pure.rb +88 -0
- data/tests/test_queue.rb +50 -0
- data/tests/test_resolver.rb +55 -0
- data/tests/test_running.rb +14 -0
- data/tests/test_sasl.rb +47 -0
- data/tests/test_send_file.rb +217 -0
- data/tests/test_servers.rb +33 -0
- data/tests/test_set_sock_opt.rb +41 -0
- data/tests/test_shutdown_hooks.rb +23 -0
- data/tests/test_smtpclient.rb +55 -0
- data/tests/test_smtpserver.rb +120 -0
- data/tests/test_spawn.rb +293 -0
- data/tests/test_ssl_args.rb +78 -0
- data/tests/test_ssl_methods.rb +48 -0
- data/tests/test_ssl_verify.rb +82 -0
- data/tests/test_threaded_resource.rb +55 -0
- data/tests/test_tick_loop.rb +59 -0
- data/tests/test_timers.rb +180 -0
- data/tests/test_ud.rb +8 -0
- data/tests/test_udp46.rb +53 -0
- data/tests/test_unbind_reason.rb +48 -0
- metadata +390 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
module Protocols
|
|
3
|
+
# Basic SOCKS v4 client implementation
|
|
4
|
+
#
|
|
5
|
+
# Use as you would any regular connection:
|
|
6
|
+
#
|
|
7
|
+
# class MyConn < EM::P::Socks4
|
|
8
|
+
# def post_init
|
|
9
|
+
# send_data("sup")
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# def receive_data(data)
|
|
13
|
+
# send_data("you said: #{data}")
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# EM.connect socks_host, socks_port, MyConn, host, port
|
|
18
|
+
#
|
|
19
|
+
class Socks4 < Connection
|
|
20
|
+
def initialize(host, port)
|
|
21
|
+
@host = Socket.gethostbyname(host).last
|
|
22
|
+
@port = port
|
|
23
|
+
@socks_error_code = nil
|
|
24
|
+
@buffer = ''
|
|
25
|
+
setup_methods
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def setup_methods
|
|
29
|
+
class << self
|
|
30
|
+
def post_init; socks_post_init; end
|
|
31
|
+
def receive_data(*a); socks_receive_data(*a); end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def restore_methods
|
|
36
|
+
class << self
|
|
37
|
+
remove_method :post_init
|
|
38
|
+
remove_method :receive_data
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def socks_post_init
|
|
43
|
+
header = [4, 1, @port, @host, 0].flatten.pack("CCnA4C")
|
|
44
|
+
send_data(header)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def socks_receive_data(data)
|
|
48
|
+
@buffer << data
|
|
49
|
+
return if @buffer.size < 8
|
|
50
|
+
|
|
51
|
+
header_resp = @buffer.slice! 0, 8
|
|
52
|
+
_, r = header_resp.unpack("cc")
|
|
53
|
+
if r != 90
|
|
54
|
+
@socks_error_code = r
|
|
55
|
+
close_connection
|
|
56
|
+
return
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
restore_methods
|
|
60
|
+
|
|
61
|
+
post_init
|
|
62
|
+
receive_data(@buffer) unless @buffer.empty?
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
|
5
|
+
# Date:: 15 November 2006
|
|
6
|
+
#
|
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
|
8
|
+
# usage examples.
|
|
9
|
+
#
|
|
10
|
+
#----------------------------------------------------------------------------
|
|
11
|
+
#
|
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
13
|
+
# Gmail: blackhedd
|
|
14
|
+
#
|
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
|
19
|
+
#
|
|
20
|
+
# See the file COPYING for complete licensing information.
|
|
21
|
+
#
|
|
22
|
+
#---------------------------------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
module EventMachine
|
|
28
|
+
module Protocols
|
|
29
|
+
|
|
30
|
+
# Implements Stomp (http://docs.codehaus.org/display/STOMP/Protocol).
|
|
31
|
+
#
|
|
32
|
+
# == Usage example
|
|
33
|
+
#
|
|
34
|
+
# module StompClient
|
|
35
|
+
# include EM::Protocols::Stomp
|
|
36
|
+
#
|
|
37
|
+
# def connection_completed
|
|
38
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# def receive_msg msg
|
|
42
|
+
# if msg.command == "CONNECTED"
|
|
43
|
+
# subscribe '/some/topic'
|
|
44
|
+
# else
|
|
45
|
+
# p ['got a message', msg]
|
|
46
|
+
# puts msg.body
|
|
47
|
+
# end
|
|
48
|
+
# end
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# EM.run{
|
|
52
|
+
# EM.connect 'localhost', 61613, StompClient
|
|
53
|
+
# }
|
|
54
|
+
#
|
|
55
|
+
module Stomp
|
|
56
|
+
include LineText2
|
|
57
|
+
|
|
58
|
+
class Message
|
|
59
|
+
# The command associated with the message, usually 'CONNECTED' or 'MESSAGE'
|
|
60
|
+
attr_accessor :command
|
|
61
|
+
# Hash containing headers such as destination and message-id
|
|
62
|
+
attr_accessor :header
|
|
63
|
+
alias :headers :header
|
|
64
|
+
# Body of the message
|
|
65
|
+
attr_accessor :body
|
|
66
|
+
|
|
67
|
+
# @private
|
|
68
|
+
def initialize
|
|
69
|
+
@header = {}
|
|
70
|
+
@state = :precommand
|
|
71
|
+
@content_length = nil
|
|
72
|
+
end
|
|
73
|
+
# @private
|
|
74
|
+
def consume_line line
|
|
75
|
+
if @state == :precommand
|
|
76
|
+
unless line =~ /\A\s*\Z/
|
|
77
|
+
@command = line
|
|
78
|
+
@state = :headers
|
|
79
|
+
end
|
|
80
|
+
elsif @state == :headers
|
|
81
|
+
if line == ""
|
|
82
|
+
if @content_length
|
|
83
|
+
yield( [:sized_text, @content_length+1] )
|
|
84
|
+
else
|
|
85
|
+
@state = :body
|
|
86
|
+
yield( [:unsized_text] )
|
|
87
|
+
end
|
|
88
|
+
elsif line =~ /\A([^:]+):(.+)\Z/
|
|
89
|
+
k = $1.dup.strip
|
|
90
|
+
v = $2.dup.strip
|
|
91
|
+
@header[k] = v
|
|
92
|
+
if k == "content-length"
|
|
93
|
+
@content_length = v.to_i
|
|
94
|
+
end
|
|
95
|
+
else
|
|
96
|
+
# This is a protocol error. How to signal it?
|
|
97
|
+
end
|
|
98
|
+
elsif @state == :body
|
|
99
|
+
@body = line
|
|
100
|
+
yield( [:dispatch] )
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @private
|
|
106
|
+
def send_frame verb, headers={}, body=""
|
|
107
|
+
ary = [verb, "\n"]
|
|
108
|
+
headers.each {|k,v| ary << "#{k}:#{v}\n" }
|
|
109
|
+
ary << "content-length: #{body.to_s.length}\n"
|
|
110
|
+
ary << "content-type: text/plain; charset=UTF-8\n" unless headers.has_key? 'content-type'
|
|
111
|
+
ary << "\n"
|
|
112
|
+
ary << body.to_s
|
|
113
|
+
ary << "\0"
|
|
114
|
+
send_data ary.join
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @private
|
|
118
|
+
def receive_line line
|
|
119
|
+
@stomp_initialized || init_message_reader
|
|
120
|
+
@stomp_message.consume_line(line) {|outcome|
|
|
121
|
+
if outcome.first == :sized_text
|
|
122
|
+
set_text_mode outcome[1]
|
|
123
|
+
elsif outcome.first == :unsized_text
|
|
124
|
+
set_delimiter "\0"
|
|
125
|
+
elsif outcome.first == :dispatch
|
|
126
|
+
receive_msg(@stomp_message) if respond_to?(:receive_msg)
|
|
127
|
+
init_message_reader
|
|
128
|
+
end
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @private
|
|
133
|
+
def receive_binary_data data
|
|
134
|
+
@stomp_message.body = data[0..-2]
|
|
135
|
+
receive_msg(@stomp_message) if respond_to?(:receive_msg)
|
|
136
|
+
init_message_reader
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @private
|
|
140
|
+
def init_message_reader
|
|
141
|
+
@stomp_initialized = true
|
|
142
|
+
set_delimiter "\n"
|
|
143
|
+
set_line_mode
|
|
144
|
+
@stomp_message = Message.new
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Invoked with an incoming Stomp::Message received from the STOMP server
|
|
148
|
+
def receive_msg msg
|
|
149
|
+
# stub, overwrite this in your handler
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# CONNECT command, for authentication
|
|
153
|
+
#
|
|
154
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
155
|
+
#
|
|
156
|
+
def connect parms={}
|
|
157
|
+
send_frame "CONNECT", parms
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# SEND command, for publishing messages to a topic
|
|
161
|
+
#
|
|
162
|
+
# send '/topic/name', 'some message here'
|
|
163
|
+
#
|
|
164
|
+
def send destination, body, parms={}
|
|
165
|
+
send_frame "SEND", parms.merge( :destination=>destination ), body.to_s
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# SUBSCRIBE command, for subscribing to topics
|
|
169
|
+
#
|
|
170
|
+
# subscribe '/topic/name', false
|
|
171
|
+
#
|
|
172
|
+
def subscribe dest, ack=false
|
|
173
|
+
send_frame "SUBSCRIBE", {:destination=>dest, :ack=>(ack ? "client" : "auto")}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# ACK command, for acknowledging receipt of messages
|
|
177
|
+
#
|
|
178
|
+
# module StompClient
|
|
179
|
+
# include EM::P::Stomp
|
|
180
|
+
#
|
|
181
|
+
# def connection_completed
|
|
182
|
+
# connect :login => 'guest', :passcode => 'guest'
|
|
183
|
+
# # subscribe with ack mode
|
|
184
|
+
# subscribe '/some/topic', true
|
|
185
|
+
# end
|
|
186
|
+
#
|
|
187
|
+
# def receive_msg msg
|
|
188
|
+
# if msg.command == "MESSAGE"
|
|
189
|
+
# ack msg.headers['message-id']
|
|
190
|
+
# puts msg.body
|
|
191
|
+
# end
|
|
192
|
+
# end
|
|
193
|
+
# end
|
|
194
|
+
#
|
|
195
|
+
def ack msgid
|
|
196
|
+
send_frame "ACK", 'message-id'=> msgid
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
|
4
|
+
# Homepage:: http://rubyeventmachine.com
|
|
5
|
+
# Date:: 16 July 2006
|
|
6
|
+
#
|
|
7
|
+
# See EventMachine and EventMachine::Connection for documentation and
|
|
8
|
+
# usage examples.
|
|
9
|
+
#
|
|
10
|
+
#----------------------------------------------------------------------------
|
|
11
|
+
#
|
|
12
|
+
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
|
13
|
+
# Gmail: blackhedd
|
|
14
|
+
#
|
|
15
|
+
# This program is free software; you can redistribute it and/or modify
|
|
16
|
+
# it under the terms of either: 1) the GNU General Public License
|
|
17
|
+
# as published by the Free Software Foundation; either version 2 of the
|
|
18
|
+
# License, or (at your option) any later version; or 2) Ruby's License.
|
|
19
|
+
#
|
|
20
|
+
# See the file COPYING for complete licensing information.
|
|
21
|
+
#
|
|
22
|
+
#---------------------------------------------------------------------------
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
module EventMachine
|
|
28
|
+
module Protocols
|
|
29
|
+
|
|
30
|
+
# @private
|
|
31
|
+
class TcpConnectTester < Connection
|
|
32
|
+
include EventMachine::Deferrable
|
|
33
|
+
|
|
34
|
+
def self.test( host, port )
|
|
35
|
+
EventMachine.connect( host, port, self )
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def post_init
|
|
39
|
+
@start_time = Time.now
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def connection_completed
|
|
43
|
+
@completed = true
|
|
44
|
+
set_deferred_status :succeeded, (Time.now - @start_time)
|
|
45
|
+
close_connection
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unbind
|
|
49
|
+
set_deferred_status :failed, (Time.now - @start_time) unless @completed
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/em/queue.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
# A cross thread, reactor scheduled, linear queue.
|
|
3
|
+
#
|
|
4
|
+
# This class provides a simple queue abstraction on top of the reactor
|
|
5
|
+
# scheduler. It services two primary purposes:
|
|
6
|
+
#
|
|
7
|
+
# * API sugar for stateful protocols
|
|
8
|
+
# * Pushing processing onto the reactor thread
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
#
|
|
12
|
+
# q = EM::Queue.new
|
|
13
|
+
# q.push('one', 'two', 'three')
|
|
14
|
+
# 3.times do
|
|
15
|
+
# q.pop { |msg| puts(msg) }
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
class Queue
|
|
19
|
+
def initialize
|
|
20
|
+
@items = []
|
|
21
|
+
@popq = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Pop items off the queue, running the block on the reactor thread. The pop
|
|
25
|
+
# will not happen immediately, but at some point in the future, either in
|
|
26
|
+
# the next tick, if the queue has data, or when the queue is populated.
|
|
27
|
+
#
|
|
28
|
+
# @return [NilClass] nil
|
|
29
|
+
def pop(*a, &b)
|
|
30
|
+
cb = EM::Callback(*a, &b)
|
|
31
|
+
EM.schedule do
|
|
32
|
+
if @items.empty?
|
|
33
|
+
@popq << cb
|
|
34
|
+
else
|
|
35
|
+
cb.call @items.shift
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
nil # Always returns nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Push items onto the queue in the reactor thread. The items will not appear
|
|
42
|
+
# in the queue immediately, but will be scheduled for addition during the
|
|
43
|
+
# next reactor tick.
|
|
44
|
+
def push(*items)
|
|
45
|
+
EM.schedule do
|
|
46
|
+
@items.push(*items)
|
|
47
|
+
@popq.shift.call @items.shift until @items.empty? || @popq.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
alias :<< :push
|
|
51
|
+
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
# @note This is a peek, it's not thread safe, and may only tend toward accuracy.
|
|
54
|
+
def empty?
|
|
55
|
+
@items.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Integer] Queue size
|
|
59
|
+
# @note This is a peek, it's not thread safe, and may only tend toward accuracy.
|
|
60
|
+
def size
|
|
61
|
+
@items.size
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Integer] Waiting size
|
|
65
|
+
# @note This is a peek at the number of jobs that are currently waiting on the Queue
|
|
66
|
+
def num_waiting
|
|
67
|
+
@popq.size
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end # Queue
|
|
71
|
+
end # EventMachine
|
data/lib/em/resolver.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module EventMachine
|
|
2
|
+
module DNS
|
|
3
|
+
class Resolver
|
|
4
|
+
|
|
5
|
+
def self.resolve(hostname)
|
|
6
|
+
Request.new(socket, hostname)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
@socket = @nameservers = nil
|
|
10
|
+
|
|
11
|
+
def self.socket
|
|
12
|
+
if !@socket || (@socket && @socket.error?)
|
|
13
|
+
@socket = Socket.open
|
|
14
|
+
|
|
15
|
+
@hosts = {}
|
|
16
|
+
IO.readlines('/etc/hosts').each do |line|
|
|
17
|
+
next if line =~ /^#/
|
|
18
|
+
addr, host = line.split(/\s+/)
|
|
19
|
+
|
|
20
|
+
if @hosts[host]
|
|
21
|
+
@hosts[host] << addr
|
|
22
|
+
else
|
|
23
|
+
@hosts[host] = [addr]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@socket
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.nameservers=(ns)
|
|
32
|
+
@nameservers = ns
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.nameservers
|
|
36
|
+
if !@nameservers
|
|
37
|
+
@nameservers = []
|
|
38
|
+
IO.readlines('/etc/resolv.conf').each do |line|
|
|
39
|
+
if line =~ /^nameserver (.+)$/
|
|
40
|
+
nscandidate = $1.split(/\s+/).first
|
|
41
|
+
@nameservers << nscandidate unless nscandidate =~ /:/ # stick with IPv4 for now
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
@nameservers
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.nameserver
|
|
49
|
+
nameservers.shuffle.first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.hosts
|
|
53
|
+
@hosts
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class RequestIdAlreadyUsed < RuntimeError; end
|
|
58
|
+
|
|
59
|
+
class Socket < EventMachine::Connection
|
|
60
|
+
def self.open
|
|
61
|
+
s = EventMachine::open_datagram_socket('0.0.0.0', 0, self) # FIXME: this is IPv4 only
|
|
62
|
+
s.send_error_handling = :ERRORHANDLING_REPORT
|
|
63
|
+
s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def initialize
|
|
67
|
+
@nameserver = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def post_init
|
|
71
|
+
@requests = {}
|
|
72
|
+
EM.add_periodic_timer(0.1, &method(:tick))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def unbind
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def tick
|
|
79
|
+
@requests.each do |id,req|
|
|
80
|
+
req.tick
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def register_request(id, req)
|
|
85
|
+
if @requests.has_key?(id)
|
|
86
|
+
raise RequestIdAlreadyUsed
|
|
87
|
+
else
|
|
88
|
+
@requests[id] = req
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def send_packet(pkt)
|
|
93
|
+
send_datagram(pkt, nameserver, 53)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def nameserver=(ns)
|
|
97
|
+
@nameserver = ns
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def nameserver
|
|
101
|
+
@nameserver || Resolver.nameserver
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Decodes the packet, looks for the request and passes the
|
|
105
|
+
# response over to the requester
|
|
106
|
+
def receive_data(data)
|
|
107
|
+
msg = nil
|
|
108
|
+
begin
|
|
109
|
+
msg = Resolv::DNS::Message.decode data
|
|
110
|
+
rescue
|
|
111
|
+
else
|
|
112
|
+
req = @requests[msg.id]
|
|
113
|
+
if req
|
|
114
|
+
@requests.delete(msg.id)
|
|
115
|
+
req.receive_answer(msg)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class Request
|
|
122
|
+
include Deferrable
|
|
123
|
+
attr_accessor :retry_interval, :max_tries
|
|
124
|
+
|
|
125
|
+
def initialize(socket, hostname)
|
|
126
|
+
@socket = socket
|
|
127
|
+
@hostname = hostname
|
|
128
|
+
@tries = 0
|
|
129
|
+
@last_send = Time.at(0)
|
|
130
|
+
@retry_interval = 3
|
|
131
|
+
@max_tries = 5
|
|
132
|
+
|
|
133
|
+
if addrs = Resolver.hosts[hostname]
|
|
134
|
+
succeed addrs
|
|
135
|
+
else
|
|
136
|
+
EM.next_tick { tick }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def tick
|
|
141
|
+
# Break early if nothing to do
|
|
142
|
+
return if @last_send + @retry_interval > Time.now
|
|
143
|
+
if @tries < @max_tries
|
|
144
|
+
send
|
|
145
|
+
else
|
|
146
|
+
fail 'retries exceeded'
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def receive_answer(msg)
|
|
151
|
+
addrs = []
|
|
152
|
+
msg.each_answer do |name,ttl,data|
|
|
153
|
+
if data.kind_of?(Resolv::DNS::Resource::IN::A) ||
|
|
154
|
+
data.kind_of?(Resolv::DNS::Resource::IN::AAAA)
|
|
155
|
+
addrs << data.address.to_s
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if addrs.empty?
|
|
160
|
+
fail "rcode=#{msg.rcode}"
|
|
161
|
+
else
|
|
162
|
+
succeed addrs
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def send
|
|
169
|
+
@tries += 1
|
|
170
|
+
@last_send = Time.now
|
|
171
|
+
@socket.send_packet(packet.encode)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def id
|
|
175
|
+
begin
|
|
176
|
+
@id = rand(65535)
|
|
177
|
+
@socket.register_request(@id, self)
|
|
178
|
+
rescue RequestIdAlreadyUsed
|
|
179
|
+
retry
|
|
180
|
+
end unless defined?(@id)
|
|
181
|
+
|
|
182
|
+
@id
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def packet
|
|
186
|
+
msg = Resolv::DNS::Message.new
|
|
187
|
+
msg.id = id
|
|
188
|
+
msg.rd = 1
|
|
189
|
+
msg.add_question @hostname, Resolv::DNS::Resource::IN::A
|
|
190
|
+
msg
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|