raptor-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,75 @@
1
+
2
+ # Communication through a SAPRouter
3
+ #
4
+ # By default SAPRouter listens on port 3299.
5
+ #
6
+ # @see https://labs.mwrinfosecurity.com/blog/2012/09/13/sap-smashing-internet-windows/
7
+ # @see http://conference.hitb.org/hitbsecconf2010ams/materials/D2T2%20-%20Mariano%20Nunez%20Di%20Croce%20-%20SAProuter%20.pdf
8
+ class RaptorIO::Socket::Comm::SAPNI < RaptorIO::Socket::Comm
9
+
10
+ # The bits of the packet that don't change
11
+ NI_ROUTE_HEADER = [
12
+ "NI_ROUTE",
13
+ 2, # route info version
14
+ 39, # NI version
15
+ 2, # number of entries
16
+ 1, # talk mode (NI_MSG_IO: 0; NI_RAW_IO; 1; NI_ROUT_IO: 2)
17
+ 0, # unused
18
+ 0, # unused
19
+ 1, # number of rest nodes
20
+ ].pack("Z*C7")
21
+
22
+ # @param options [Hash]
23
+ # @option options :sap_host [String,IPAddr]
24
+ # @option options :sap_port [Fixnum] (3299)
25
+ # @option options :sap_comm [Comm]
26
+ def initialize(options = {})
27
+ @sap_host = options[:sap_host]
28
+ @sap_port = (options[:sap_port] || 3299).to_i
29
+ @sap_comm = options[:sap_comm]
30
+ end
31
+
32
+ # Connect to a SAPRouter and use its routing capabilities to create a
33
+ # TCP connection to `:peer_host`.
34
+ #
35
+ # @param (see Comm#create_tcp)
36
+ def create_tcp(options)
37
+ @sap_socket = @sap_comm.create_tcp(
38
+ peer_host: @sap_host,
39
+ peer_port: @sap_port
40
+ )
41
+
42
+ first_route_item = [
43
+ @sap_host, @sap_port.to_s, 0
44
+ ].pack("Z*Z*C")
45
+
46
+ second_route_item = [
47
+ options[:peer_host], options[:peer_port].to_s, 0
48
+ ].pack("Z*Z*C")
49
+
50
+ route_data =
51
+ # This is *not* a length, it is the
52
+ # "current position as an offset into the route string"
53
+ # according to
54
+ # http://help.sap.com/saphelp_nwpi711/helpdata/en/48/6a29785bed4e6be10000000a421937/content.htm
55
+ [ first_route_item.length ].pack("N") +
56
+ first_route_item +
57
+ second_route_item
58
+ route_data = [ route_data.length - 4 ].pack("N") + route_data
59
+
60
+ ni_packet = NI_ROUTE_HEADER.dup + route_data
61
+ ni_packet = [ni_packet.length].pack('N') + ni_packet
62
+
63
+ @sap_socket.write(ni_packet)
64
+ res_length = @sap_socket.read(4)
65
+ res = @sap_socket.read(res_length.unpack("N").first)
66
+
67
+ unless res == "NI_PONG\x00"
68
+ raise RaptorIO::Socket::Error::ConnectionError
69
+ end
70
+
71
+ RaptorIO::Socket::TCP.new(@sap_socket, options)
72
+ end
73
+
74
+ end
75
+
@@ -0,0 +1,237 @@
1
+ require 'timeout'
2
+ require 'socket'
3
+
4
+ # Communication through a SOCKS proxy
5
+ #
6
+ # @see http://openssh.org/txt/socks4.protocol
7
+ # @see https://tools.ietf.org/html/rfc1928
8
+ class RaptorIO::Socket::Comm::SOCKS < RaptorIO::Socket::Comm
9
+
10
+ # @!attribute socks_host
11
+ # The SOCKS server's address
12
+ # @return [String]
13
+ attr_accessor :socks_host
14
+ # @!attribute socks_port
15
+ # The SOCKS server's port
16
+ # @return [Fixnum]
17
+ attr_accessor :socks_port
18
+ # @!attribute socks_comm
19
+ # The {Comm} used to connect to the SOCKS server
20
+ # @return [Comm]
21
+ attr_accessor :socks_comm
22
+
23
+ # Constants for address types ("ATYP" in the RFC)
24
+ module AddressTypes
25
+ # 4-byte IPv4 address
26
+ ATYP_IPv4 = 1
27
+ # DNS name as a Pascal string
28
+ ATYP_DOMAINNAME = 3
29
+ # 16-byte IPv6 address
30
+ ATYP_IPv6 = 4
31
+ end
32
+
33
+ # Constants for reply codes
34
+ module ReplyCodes
35
+ # `X'00' succeeded`
36
+ SUCCEEDED = 0
37
+ # `X'01' general SOCKS server failure`
38
+ GENERAL_FAILURE = 1
39
+ # `X'02' connection not allowed by ruleset`
40
+ NOT_ALLOWED = 2
41
+ # `X'03' Network unreachable`
42
+ NETUNREACH = 3
43
+ # `X'04' Host unreachable`
44
+ HOSTUNREACH = 4
45
+ # `X'05' Connection refused`
46
+ CONNREFUSED = 5
47
+ # `X'06' TTL expired`
48
+ TTL_EXPIRED = 6
49
+ # `X'07' Command not supported`
50
+ CMD_NOT_SUPPORTED = 7
51
+ # `X'08' Address type not supported`
52
+ ATYP_NOT_SUPPORTED = 8
53
+ end
54
+
55
+ # @param options [Hash]
56
+ # @option options :socks_host [String,IPAddr]
57
+ # @option options :socks_port [Fixnum]
58
+ # @option options :socks_comm [Comm]
59
+ def initialize(options = {})
60
+ @socks_host = options[:socks_host]
61
+ @socks_port = options[:socks_port].to_i
62
+ @socks_comm = options[:socks_comm]
63
+ end
64
+
65
+ # (see Comm#support_ipv6?)
66
+ def support_ipv6?
67
+ begin
68
+ tcp = create_tcp("::1", {})
69
+ tcp.close
70
+ true
71
+ rescue RaptorIO::Error
72
+ nil
73
+ end
74
+ end
75
+
76
+ # Connect to `:peer_host`
77
+ #
78
+ # @option (see Comm#create_tcp)
79
+ #
80
+ # @return [Socket::TCP]
81
+ #
82
+ # @raise [RaptorIO::Socket::Error::ConnectTimeout]
83
+ def create_tcp(options)
84
+ @socks_socket = socks_comm.create_tcp(
85
+ peer_host: socks_host,
86
+ peer_port: socks_port
87
+ )
88
+
89
+ negotiate_connection(options[:peer_host], options[:peer_port])
90
+
91
+ if options[:ssl_context]
92
+ RaptorIO::Socket::TCP::SSL.new(@socks_socket, options)
93
+ else
94
+ RaptorIO::Socket::TCP.new(@socks_socket, options)
95
+ end
96
+ end
97
+
98
+
99
+ private
100
+
101
+ # Attempt to create a connection to `peer_host`:`peer_port` via the
102
+ # SOCKS server at {#socks_host}:{#socks_port}.
103
+ #
104
+ # @param peer_host [String] An address or hostname
105
+ # @param peer_port [Fixnum] TCP port to connect to
106
+ #
107
+ # @raise [Error::ConnectionError] When the connection fails
108
+ def negotiate_connection(peer_host, peer_port)
109
+ # From RFC1928:
110
+ # ```
111
+ # o X'00' NO AUTHENTICATION REQUIRED
112
+ # o X'01' GSSAPI
113
+ # o X'02' USERNAME/PASSWORD
114
+ # o X'03' to X'7F' IANA ASSIGNED
115
+ # o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
116
+ # o X'FF' NO ACCEPTABLE METHODS
117
+ # ```
118
+ auth_methods = [ 0 ]
119
+ # [ version ][ N methods ][ methods ... ]
120
+ v5_pkt = [ 5, auth_methods.count, *auth_methods ].pack("CCC*")
121
+
122
+ @socks_socket.write(v5_pkt)
123
+ response = @socks_socket.read(2)
124
+
125
+ case response
126
+ when "\x05\x00".force_encoding('binary')
127
+ # Then they accepted NO AUTHENTICATION and we can send a connect
128
+ # request *without* a password
129
+ request = pack_v5_connect_packet(peer_host, peer_port.to_i)
130
+ else
131
+ # Then they didn't like what we had to offer.
132
+ @socks_socket.close
133
+ raise RaptorIO::Socket::Error::ConnectionError, "Proxy connection failed"
134
+ end
135
+
136
+ @socks_socket.write(request)
137
+
138
+ reply_pkt = @socks_socket.read(4)
139
+ if reply_pkt.nil?
140
+ # ssh(1) likes to just sever the connection if it can't connect
141
+ raise RaptorIO::Socket::Error::ConnectionError
142
+ end
143
+
144
+ handle_reply(reply_pkt)
145
+ end
146
+
147
+ def pack_v5_connect_packet(peer_host, peer_port)
148
+ begin
149
+ ip = IPAddr.parse(peer_host)
150
+ rescue ArgumentError
151
+ type = AddressTypes::ATYP_DOMAINNAME
152
+ # Packed as a Pascal string
153
+ packed_addr = [peer_host.length, peer_host].pack("Ca*")
154
+ else
155
+ if ip.to_range.count != 1
156
+ raise ArgumentError, "Invalid host"
157
+ end
158
+ type = if ip.ipv4?
159
+ AddressTypes::ATYP_IPv4
160
+ elsif ip.ipv6?
161
+ AddressTypes::ATYP_IPv6
162
+ end
163
+ packed_addr = ip.hton
164
+ end
165
+ connect_packet = [
166
+ 5, # Version
167
+ 1, # CMD, CONNECT X'01'
168
+ 0, # reserved
169
+ type,
170
+ packed_addr,
171
+ peer_port
172
+ ].pack("CCCCa*n")
173
+
174
+ connect_packet
175
+ end
176
+
177
+ def handle_reply(reply_pkt)
178
+
179
+ # [ version ][ reply code ][ reserved ][ atyp ]
180
+ _, reply, _, type = reply_pkt.unpack("C4")
181
+
182
+ # X'00' succeeded
183
+ # X'01' general SOCKS server failure
184
+ # X'02' connection not allowed by ruleset
185
+ # X'03' Network unreachable
186
+ # X'04' Host unreachable
187
+ # X'05' Connection refused
188
+ # X'06' TTL expired
189
+ # X'07' Command not supported
190
+ # X'08' Address type not supported
191
+ # X'09' to X'FF' unassigned
192
+ case reply
193
+ when ReplyCodes::SUCCEEDED
194
+ # Read in the bind addr. The protocol spec says this is supposed
195
+ # to be the getsockname(2) address of the sockfd on the server,
196
+ # which isn't all that useful to begin with. SSH(1) always
197
+ # populates it with NULL bytes, making it completely pointless.
198
+ # Read it off the socket and ignore it so it doesn't get in the
199
+ # way of the proxied traffic.
200
+ case type
201
+ when AddressTypes::ATYP_IPv4
202
+ @socks_socket.read(4)
203
+ when AddressTypes::ATYP_IPv6
204
+ @socks_socket.read(16)
205
+ when AddressTypes::ATYP_DOMAINNAME
206
+ # Pascal string, so read in the length and then read that many
207
+ len = @socks_socket.read(1).to_i
208
+ @socks_socket.read(len)
209
+ end
210
+ # bind port
211
+ @socks_socket.read(2)
212
+
213
+ when ReplyCodes::NETUNREACH, ReplyCodes::HOSTUNREACH
214
+ @socks_socket.close
215
+ raise RaptorIO::Socket::Error::HostUnreachable
216
+ when ReplyCodes::CONNREFUSED
217
+ @socks_socket.close
218
+ raise RaptorIO::Socket::Error::ConnectionRefused
219
+ when ReplyCodes::GENERAL_FAILURE,
220
+ ReplyCodes::NOT_ALLOWED,
221
+ ReplyCodes::TTL_EXPIRED,
222
+ ReplyCodes::CMD_NOT_SUPPORTED,
223
+ ReplyCodes::ATYP_NOT_SUPPORTED
224
+ # Then this is a kind of failure that doesn't map well to standard
225
+ # socket errors. Just call it a ConnectionError.
226
+ @socks_socket.close
227
+ raise RaptorIO::Socket::Error::ConnectionError
228
+ else
229
+ # Then this is an unassigned error code. No idea what it is, so
230
+ # just call it a ConnectionError
231
+ @socks_socket.close
232
+ raise RaptorIO::Socket::Error::ConnectionError
233
+ end
234
+ end
235
+
236
+ end
237
+
@@ -0,0 +1,30 @@
1
+
2
+ class RaptorIO::Socket::CommChain
3
+ attr_accessor :comms
4
+
5
+ # @param uris [Array] A list of URIs (as `URI` objects or as `String`s)
6
+ def initialize(*uris)
7
+ @comms = [ RaptorIO::Socket::SwitchBoard::DEFAULT_ROUTE.comm ]
8
+ uris.each do |arg|
9
+ begin
10
+ arg_uri = (arg.kind_of? URI) ? arg : URI.parse(arg)
11
+ rescue URI::InvalidURIError
12
+ raise ArgumentError.new("Invalid URI (#{arg.inspect})")
13
+ end
14
+
15
+ next_comm = RaptorIO::Socket::Comm.from_uri(arg_uri, prev_comm: @comms.last)
16
+
17
+ if next_comm.kind_of? RaptorIO::Socket::Comm
18
+ @comms << next_comm
19
+ else
20
+ raise ArgumentError.new("Invalid Comm: unknown scheme (#{arg_uri.scheme.inspect})")
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ def create_tcp(opts = {})
27
+ @comms.last.create_tcp(opts)
28
+ end
29
+
30
+ end
@@ -0,0 +1,45 @@
1
+ # Base class for all socket-related errors
2
+ class RaptorIO::Socket::Error < RaptorIO::Error
3
+
4
+ # Hostname resolution error.
5
+ #
6
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
7
+ class CouldNotResolve < RaptorIO::Socket::Error
8
+ end
9
+
10
+ # Base class for errors that cause a connection to fail.
11
+ class ConnectionError < RaptorIO::Socket::Error
12
+ end
13
+
14
+ # Raised when a socket receives no SYN/ACK before timeout.
15
+ class ConnectionTimeout < RaptorIO::Socket::Error::ConnectionError
16
+ end
17
+
18
+ # Raised when a socket receives a RST during connect.
19
+ class ConnectionRefused < RaptorIO::Socket::Error::ConnectionError
20
+ end
21
+
22
+ # Host reachability error.
23
+ #
24
+ # Occurs when the remote host cannot be reached.
25
+ #
26
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
27
+ class HostUnreachable < RaptorIO::Socket::Error::ConnectionError
28
+ end
29
+
30
+ # Broken-pipe error.
31
+ #
32
+ # Occurs when a connection dies unexpectedly.
33
+ #
34
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
35
+ class BrokenPipe < RaptorIO::Socket::Error
36
+ end
37
+
38
+ # Not connected error.
39
+ #
40
+ # Occurs when attempting to transmit data over a not connected transport endpoint.
41
+ #
42
+ # @author Tasos "Zapotek" Laskos <tasos.laskos@gmail.com>
43
+ class NotConnected < RaptorIO::Socket::Error
44
+ end
45
+ end
@@ -0,0 +1,183 @@
1
+ # -*- coding: binary -*-
2
+
3
+ require 'thread'
4
+
5
+ ###
6
+ #
7
+ # A routing table that associates subnets with {Comm} objects. Comm
8
+ # classes are used to instantiate objects that are tied to remote
9
+ # network entities. For example, {Comm::Local} is used to build network
10
+ # connections directly from the local machine whereas, for instance, a
11
+ # SOCKS Comm would build a local socket pair that is associated with a
12
+ # connection established by a remote SOCKS server. This can be seen as
13
+ # a uniform way of communicating with hosts through arbitrary channels.
14
+ #
15
+ ###
16
+ class RaptorIO::Socket::SwitchBoard
17
+
18
+ require 'raptor-io/socket/comm'
19
+ require 'raptor-io/socket/comm_chain'
20
+ require 'raptor-io/socket/switch_board/route'
21
+
22
+ include Enumerable
23
+
24
+ # If no route matches the host/netmask when searching for
25
+ # {#best_comm}, this will be the fallback - always route through the
26
+ # local machine creating Ruby ::Sockets.
27
+ DEFAULT_ROUTE = Route.new("0.0.0.0", "0.0.0.0", RaptorIO::Socket::Comm::Local.new)
28
+
29
+ # The list of routes this swithboard knows about
30
+ #
31
+ # @return [Array<Route>]
32
+ attr_reader :routes
33
+
34
+ def initialize
35
+ @routes = Array.new
36
+ @mutex = Mutex.new
37
+ end
38
+
39
+ # Adds a route for a given subnet and netmask destined through a given comm
40
+ # instance.
41
+ #
42
+ # @param (see RaptorIO::Socket::SwitchBoard::Route#new)
43
+ # @return [Boolean] Whether the route was added. This may fail if a
44
+ # route already {#route_exists? existed} or if the given `comm` does
45
+ # not support the address family of the `subnet` (e.g., an IPv6
46
+ # address for a comm that does not support IPv6)
47
+ def add_route(subnet, netmask, comm)
48
+ rv = true
49
+ subnet = IPAddr.parse(subnet)
50
+ if subnet.ipv6? and comm.respond_to?(:support_ipv6?)
51
+ return false unless comm.support_ipv6?
52
+ end
53
+
54
+ synchronize do
55
+ # If the route already exists, return false to the caller.
56
+ if route_exists?(subnet, netmask)
57
+ rv = false
58
+ else
59
+ @routes << Route.new(subnet, netmask, comm)
60
+ end
61
+ end
62
+
63
+ rv
64
+ end
65
+
66
+ # Finds the best possible comm for the supplied target address.
67
+ #
68
+ # @param addr [String,IPAddr] The address to which we want to talk
69
+ # @return [Comm]
70
+ def best_comm(addr)
71
+ addr = IPAddr.parse(addr)
72
+
73
+ # Find the most specific route that this address fits in. If none,
74
+ # use the default, i.e., local.
75
+ best_route = reduce(DEFAULT_ROUTE) do |best, route|
76
+ if route.subnet.include?(addr) && route.netmask >= best.netmask
77
+ route
78
+ else
79
+ best
80
+ end
81
+ end
82
+
83
+ best_route.comm
84
+ end
85
+
86
+ # Enumerates each entry in the routing table.
87
+ #
88
+ def each(&block)
89
+ @routes.each(&block)
90
+ end
91
+
92
+ alias each_route each
93
+
94
+ # Clears all established routes.
95
+ #
96
+ # @return [void]
97
+ def flush_routes
98
+ # Remove each of the individual routes so the comms don't think they're
99
+ # still routing after a flush.
100
+ @routes.each do |r|
101
+ if r.comm.respond_to? :routes
102
+ r.comm.routes.delete("#{r.subnet}/#{r.netmask}")
103
+ end
104
+ end
105
+
106
+ # Re-initialize to an empty array
107
+ @routes.clear
108
+ end
109
+
110
+ #
111
+ # Remove all routes that go through the supplied `comm`.
112
+ #
113
+ # @param comm [Comm]
114
+ # @return [void]
115
+ def remove_by_comm(comm)
116
+ synchronize do
117
+ @routes.delete_if { |route| route.comm == comm }
118
+ end
119
+
120
+ nil
121
+ end
122
+
123
+ #
124
+ # Removes a route for a given subnet and netmask destined through a given
125
+ # comm instance.
126
+ #
127
+ # @param (see Route.new)
128
+ # @return [Boolean] Whether we found one to delete
129
+ def remove_route(subnet, netmask, comm)
130
+ rv = false
131
+
132
+ other = Route.new(subnet, netmask, comm)
133
+ synchronize do
134
+ @routes.delete_if do |route|
135
+ if route == other
136
+ rv = true
137
+ else
138
+ false
139
+ end
140
+ end
141
+ end
142
+
143
+ rv
144
+ end
145
+
146
+ #
147
+ # Checks to see if a route already exists for the supplied subnet and
148
+ # netmask.
149
+ #
150
+ def route_exists?(subnet, netmask)
151
+ each do |route|
152
+ return true if route.subnet == subnet and route.netmask == netmask
153
+ end
154
+
155
+ false
156
+ end
157
+
158
+ # Create a TCP client socket on the {#best_comm best comm} available
159
+ # for `:peer_host`.
160
+ #
161
+ # @param (see Comm#create_tcp)
162
+ # @option opts (see Comm#create_tcp)
163
+ def create_tcp(opts)
164
+ best_comm(opts[:peer_host]).create_tcp(opts)
165
+ end
166
+
167
+ # Create a TCP server socket on the {#best_comm best comm} available
168
+ # for `:local_host`.
169
+ #
170
+ # @param (see Comm#create_tcp_server)
171
+ # @option opts (see Comm#create_tcp_server)
172
+ def create_tcp_server(opts)
173
+ best_comm(opts[:local_host]).create_tcp_server(opts)
174
+ end
175
+
176
+ private
177
+
178
+ def synchronize( &block )
179
+ @mutex.synchronize( &block )
180
+ end
181
+
182
+ end
183
+