firescan 0.07
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/LICENSE.txt +202 -0
- data/README.txt +37 -0
- data/USAGE.txt +43 -0
- data/VERSION.txt +4 -0
- data/bin/firescan +4 -0
- data/build +7 -0
- data/doc/Example.html +224 -0
- data/doc/Firebind.html +213 -0
- data/doc/Firebind/Client.html +750 -0
- data/doc/Firebind/Portspec.html +390 -0
- data/doc/Firebind/Scan.html +413 -0
- data/doc/Firebind/ScanError.html +277 -0
- data/doc/Firebind/ScanState.html +1015 -0
- data/doc/Firebind/SimpleProtocol.html +292 -0
- data/doc/Firebind/TcpTransport.html +481 -0
- data/doc/Firebind/Transport.html +275 -0
- data/doc/Firebind/UdpTransport.html +472 -0
- data/doc/LICENSE_txt.html +323 -0
- data/doc/README_txt.html +171 -0
- data/doc/Runner.html +166 -0
- data/doc/TestPortspec.html +289 -0
- data/doc/Tools.html +321 -0
- data/doc/USAGE_txt.html +184 -0
- data/doc/VERSION_txt.html +126 -0
- data/doc/build.html +125 -0
- data/doc/created.rid +22 -0
- data/doc/doc/created_rid.html +121 -0
- data/doc/firescan_gemspec.html +140 -0
- data/doc/images/add.png +0 -0
- data/doc/images/arrow_up.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +118 -0
- data/doc/js/darkfish.js +155 -0
- data/doc/js/jquery.js +18 -0
- data/doc/js/navigation.js +142 -0
- data/doc/js/search.js +94 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/searcher.js +228 -0
- data/doc/rdoc.css +595 -0
- data/doc/table_of_contents.html +230 -0
- data/firescan.gemspec +18 -0
- data/lib/example.rb +20 -0
- data/lib/firebind/client.rb +308 -0
- data/lib/firebind/portspec.rb +107 -0
- data/lib/firebind/scan.rb +288 -0
- data/lib/firebind/scan_error.rb +32 -0
- data/lib/firebind/scan_state.rb +140 -0
- data/lib/firebind/simple_protocol.rb +59 -0
- data/lib/firebind/tcp_transport.rb +184 -0
- data/lib/firebind/tools.rb +107 -0
- data/lib/firebind/transport.rb +44 -0
- data/lib/firebind/udp_transport.rb +174 -0
- data/test/portspec_test.rb +53 -0
- data/test/runner.rb +7 -0
- metadata +136 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
# Firebind -- Path Scan Client Software
|
2
|
+
# Copyright (C) 2013 Firebind Inc. All rights reserved.
|
3
|
+
# Authors - Jay Houghton
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require_relative 'tcp_transport'
|
18
|
+
require_relative 'udp_transport'
|
19
|
+
require_relative 'tools'
|
20
|
+
|
21
|
+
module Firebind
|
22
|
+
|
23
|
+
# Simplest of all echo protocols. This simply sends and receives an 8-byte payload representing the
|
24
|
+
# ID number of the scan that was assigned during the Scan API call.
|
25
|
+
class SimpleProtocol
|
26
|
+
|
27
|
+
include Tools
|
28
|
+
|
29
|
+
# @param [Object] transport
|
30
|
+
def initialize(guid,echo_host,transport,timeout,state=NIL)
|
31
|
+
@guid = guid
|
32
|
+
@echo_host = echo_host
|
33
|
+
@state = state
|
34
|
+
case transport
|
35
|
+
when :TCP
|
36
|
+
@transport = Firebind::TcpTransport.new @echo_host,timeout
|
37
|
+
when :UDP
|
38
|
+
@transport = Firebind::UdpTransport.new @echo_host,timeout
|
39
|
+
else
|
40
|
+
@transport = Firebind::TcpTransport.new @echo_host,timeout
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def echo(port)
|
45
|
+
payload = big_endian @guid
|
46
|
+
begin
|
47
|
+
@transport.connect port # connect
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
@transport.send payload # send
|
52
|
+
@transport.receive # receive
|
53
|
+
ensure
|
54
|
+
@transport.close # close
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# Firebind -- Path Scan Client Software
|
2
|
+
# Copyright (C) 2013 Firebind Inc. All rights reserved.
|
3
|
+
# Authors - Jay Houghton
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require_relative 'transport'
|
18
|
+
require_relative 'tools'
|
19
|
+
require_relative 'scan_error'
|
20
|
+
|
21
|
+
module Firebind
|
22
|
+
|
23
|
+
# Send and receive echo using TCP transport.
|
24
|
+
class TcpTransport < Transport
|
25
|
+
include Tools
|
26
|
+
def initialize (echo_server,timeout)
|
27
|
+
super(echo_server,timeout)
|
28
|
+
end
|
29
|
+
|
30
|
+
# establish connection to echo server on port
|
31
|
+
def connect(port)
|
32
|
+
@port = port
|
33
|
+
connect_start = Time.now
|
34
|
+
#host_addr = Socket.getaddrinfo(@echo_server,nil) # probably should get 0,3 from this guy for IPv6
|
35
|
+
|
36
|
+
begin
|
37
|
+
#noinspection RubyArgCount
|
38
|
+
@socket = Socket.new(:INET, :STREAM)
|
39
|
+
@remote_addr = Socket.pack_sockaddr_in(@port, @echo_server)
|
40
|
+
rescue
|
41
|
+
raise Firebind::ScanError.new :HANDSHAKE_CONNECTION_INITIATION_FAILURE
|
42
|
+
end
|
43
|
+
|
44
|
+
begin
|
45
|
+
@socket.connect_nonblock @remote_addr
|
46
|
+
rescue Errno::EINPROGRESS
|
47
|
+
# still doing handshake, wait @timeout until its ready to connect_nonblock again
|
48
|
+
debug("connection still inprogress select for #{@timeout}ms")
|
49
|
+
|
50
|
+
# interest ops: reading,writing,error,timeout
|
51
|
+
readables,writables,errors = IO.select([@socket], [@socket], [@socket], @timeout_seconds)
|
52
|
+
debug "readables=#{readables} writables=#{writables} errors=#{errors}"
|
53
|
+
|
54
|
+
readable = readables != NIL && readables.length > 0
|
55
|
+
writable = writables != NIL && writables.length > 0
|
56
|
+
error = errors != NIL && errors.length > 0
|
57
|
+
|
58
|
+
if error
|
59
|
+
raise Firebind::ScanError.new(:HANDSHAKE_CONNECTION_REFUSED)
|
60
|
+
end
|
61
|
+
|
62
|
+
if readable || writable
|
63
|
+
retry
|
64
|
+
else
|
65
|
+
debug "connection timeout after #{@timeout}ms"
|
66
|
+
raise Firebind::ScanError.new :HANDSHAKE_CONNECTION_TIME_OUT
|
67
|
+
end
|
68
|
+
|
69
|
+
# A time check may be required in the future (if we select for less than timeout)
|
70
|
+
# if Time.now - connect_start > @timeout_seconds
|
71
|
+
# raise Firebind::ScanError.new :HANDSHAKE_CONNECTION_TIME_OUT
|
72
|
+
|
73
|
+
rescue Errno::EISCONN # this is a linux code... todo: figure the platform codes for continuation
|
74
|
+
debug 'connection success'
|
75
|
+
rescue Errno::ECONNREFUSED => e
|
76
|
+
raise Firebind::ScanError.new(:HANDSHAKE_CONNECTION_REFUSED, e)
|
77
|
+
rescue
|
78
|
+
#collect all the ways a connection can fail... some of the conditions are:
|
79
|
+
# 1) No route to host - connect(2)
|
80
|
+
raise Firebind::ScanError.new(:HANDSHAKE_CONNECTION_REFUSED, $!)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Send data to the echo server
|
85
|
+
#
|
86
|
+
# @param [Array[]] payload - array of bytes
|
87
|
+
def send(payload)
|
88
|
+
@payload = payload
|
89
|
+
size = payload.length
|
90
|
+
count = 0
|
91
|
+
send_start = Time.now
|
92
|
+
|
93
|
+
payload.each do |byte|
|
94
|
+
begin
|
95
|
+
count += @socket.write_nonblock [byte].pack('C')
|
96
|
+
debug "wrote #{count} bytes to #{@echo_server}:#{@port}"
|
97
|
+
rescue IO::WaitWritable, Errno::EINTR
|
98
|
+
if count == size
|
99
|
+
# we've written our payload, we're done
|
100
|
+
debug "completed #{count} byte payload to #{@echo_server}:#{@port}"
|
101
|
+
elsif Time.now - send_start > @timeout_seconds
|
102
|
+
# we've timed out of sending
|
103
|
+
raise Firebind::ScanError.new :FAILURE_ON_PAYLOAD_SEND
|
104
|
+
else
|
105
|
+
# try to write some more ? http://ruby-doc.org/core-1.9.3/IO.html#method-i-write_nonblock
|
106
|
+
retry
|
107
|
+
end
|
108
|
+
|
109
|
+
rescue
|
110
|
+
#collect all the ways a send can fail...
|
111
|
+
raise Firebind::ScanError.new(:FAILURE_ON_PAYLOAD_SEND, $!)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Receive echo response from server.
|
118
|
+
#
|
119
|
+
#
|
120
|
+
# PAYLOAD_REFUSED_ON_RECV
|
121
|
+
# PAYLOAD_ERROR_ON_RECV
|
122
|
+
# PAYLOAD_TIMED_OUT_ON_RECV
|
123
|
+
#
|
124
|
+
# @return [Object]
|
125
|
+
def receive
|
126
|
+
size = @payload.length
|
127
|
+
count = 0
|
128
|
+
receive_start = Time.now
|
129
|
+
receive_buffer = ''
|
130
|
+
|
131
|
+
while Time.now - receive_start <= @timeout_seconds && count < size
|
132
|
+
begin
|
133
|
+
read_result = @socket.read_nonblock(1024)
|
134
|
+
count += read_result.length # because appending to receive_buffer won't count recv bytes via .length
|
135
|
+
# add read_result to our buffer
|
136
|
+
receive_buffer << read_result
|
137
|
+
debug "read buffer is #{count}"
|
138
|
+
rescue IO::WaitReadable
|
139
|
+
if count == size
|
140
|
+
# we're done - read all we had expected to receive
|
141
|
+
debug "completed full read of #{count} bytes"
|
142
|
+
break
|
143
|
+
elsif Time.now - receive_start > @timeout_seconds
|
144
|
+
# timeout on receive
|
145
|
+
raise Firebind::ScanError.new(:PAYLOAD_TIMED_OUT_ON_RECV)
|
146
|
+
else
|
147
|
+
# keep going
|
148
|
+
IO.select([@socket],nil,nil,@timeout_seconds)
|
149
|
+
retry
|
150
|
+
end
|
151
|
+
rescue EOFError => e
|
152
|
+
# connection was forcefully dropped or sent a final FIN ACK closing sequence
|
153
|
+
if count != size
|
154
|
+
raise Firebind::ScanError.new(:PAYLOAD_ERROR_ON_RECV,e)
|
155
|
+
end
|
156
|
+
|
157
|
+
rescue
|
158
|
+
#collect all the ways a receive can fail... todo:
|
159
|
+
if $debug
|
160
|
+
puts 'Caught unhandled socket error:'
|
161
|
+
p $1
|
162
|
+
puts $@
|
163
|
+
end
|
164
|
+
raise Firebind::ScanError.new(:PAYLOAD_REFUSED_ON_RECV,$!)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# we still could have timed out here, check rx buffer size first
|
169
|
+
if count < size
|
170
|
+
raise Firebind::ScanError.new(:PAYLOAD_TIMED_OUT_ON_RECV)
|
171
|
+
end
|
172
|
+
|
173
|
+
# now compare what we received with what we sent
|
174
|
+
received_array = receive_buffer.unpack('C*')
|
175
|
+
if received_array != @payload
|
176
|
+
debug "rx buffer #{received_array.to_s} is NOT equal to expected #{@payload.to_s}"
|
177
|
+
raise Firebind::ScanError.new(:PAYLOAD_MISMATCH_ON_RECV)
|
178
|
+
else
|
179
|
+
debug "rx buffer #{received_array.to_s} is equal to payload #{@payload.to_s}"
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Firebind -- Path Scan Client Software
|
2
|
+
# Copyright (C) 2013 Firebind Inc. All rights reserved.
|
3
|
+
# Authors - Jay Houghton
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
# Utility module for translating socket and result codes.
|
18
|
+
module Tools
|
19
|
+
|
20
|
+
$debug = false
|
21
|
+
$verbose = false
|
22
|
+
|
23
|
+
def big_endian(a_number)
|
24
|
+
bytes = []
|
25
|
+
7.downto(0) do |i|
|
26
|
+
bytes[i] = 0xFF & a_number
|
27
|
+
a_number = a_number >> 8
|
28
|
+
end
|
29
|
+
bytes
|
30
|
+
end
|
31
|
+
|
32
|
+
# @@SOCKET_CODES = { :CONNECTION_REFUSED => }
|
33
|
+
|
34
|
+
#
|
35
|
+
# Result Codes as defined in reference documents
|
36
|
+
# http://www.firebind.com/support/reference/socket_mapping.html
|
37
|
+
#
|
38
|
+
$handshake_connection_refused = 12152
|
39
|
+
$handshake_connection_timeout = 12151
|
40
|
+
#noinspection RubyGlobalVariableNamingConvention
|
41
|
+
$handshake_connection_initiation_failure = 12150
|
42
|
+
$payload_refused_on_recv = 12156
|
43
|
+
$payload_timed_out_on_recv = 12155
|
44
|
+
$payload_error_on_recv = 12158
|
45
|
+
$payload_mismatch_on_recv = 12157
|
46
|
+
$failure_on_payload_send = 12154
|
47
|
+
#noinspection RubyGlobalVariableNamingConvention
|
48
|
+
$handshake_connection_completion_failure = 12153
|
49
|
+
$success = 12110
|
50
|
+
$skipped = 12020
|
51
|
+
$scan_failure = 12111
|
52
|
+
$client_network_failure = 12112
|
53
|
+
$command_server_unavailable = 7
|
54
|
+
|
55
|
+
$result_codes = {HANDSHAKE_CONNECTION_REFUSED: $handshake_connection_refused,
|
56
|
+
HANDSHAKE_CONNECTION_TIME_OUT: $handshake_connection_timeout,
|
57
|
+
HANDSHAKE_CONNECTION_INITIATION_FAILURE: $handshake_connection_initiation_failure,
|
58
|
+
PAYLOAD_REFUSED_ON_RECV: $payload_refused_on_recv,
|
59
|
+
PAYLOAD_TIMED_OUT_ON_RECV: $payload_timed_out_on_recv,
|
60
|
+
PAYLOAD_ERROR_ON_RECV: $payload_error_on_recv,
|
61
|
+
PAYLOAD_MISMATCH_ON_RECV: $payload_mismatch_on_recv,
|
62
|
+
FAILURE_ON_PAYLOAD_SEND: $failure_on_payload_send,
|
63
|
+
HANDSHAKE_CONNECTION_COMPLETION_FAILURE: $handshake_connection_completion_failure,
|
64
|
+
SUCCESS: $success,
|
65
|
+
SKIPPED: $skipped,
|
66
|
+
TEST_FAILURE: $scan_failure,
|
67
|
+
CLIENT_NETWORK_FAILURE: $client_network_failure
|
68
|
+
}
|
69
|
+
|
70
|
+
# todo: finish reverse result code / symbol mapping
|
71
|
+
|
72
|
+
#result_code_rev = { $handshake_connection_refused => :HANDSHAKE_CONNECTION_REFUSED }
|
73
|
+
|
74
|
+
$result_code_messages = { $handshake_connection_refused => 'Handshake Connection Refused',
|
75
|
+
$handshake_connection_timeout => 'Handshake Connection Timeout',
|
76
|
+
$handshake_connection_initiation_failure => 'Handshake Connection Initiation Failure',
|
77
|
+
$payload_refused_on_recv => 'Payload Refused On Receive',
|
78
|
+
$payload_timed_out_on_recv => 'Payload Timed Out On Receive',
|
79
|
+
$payload_error_on_recv => 'Payload Error On Receive',
|
80
|
+
$payload_mismatch_on_recv => 'Payload Mismatch On Receive',
|
81
|
+
$failure_on_payload_send => 'Failure On Payload Send',
|
82
|
+
$handshake_connection_completion_failure => 'Handshake Connection Completion Failure',
|
83
|
+
$success => 'Open',
|
84
|
+
$skipped => 'Skipped',
|
85
|
+
$scan_failure => 'Scan Failure',
|
86
|
+
$client_network_failure => 'Client Network Failure'
|
87
|
+
}
|
88
|
+
|
89
|
+
# server status codes
|
90
|
+
$authentication_failure = 20401
|
91
|
+
$request_invalid = 20400
|
92
|
+
$server_bind_error = 2017
|
93
|
+
$client_scan_completed = 12021
|
94
|
+
|
95
|
+
def verbose(msg)
|
96
|
+
puts "Firescan - #{msg}" if $verbose
|
97
|
+
end
|
98
|
+
|
99
|
+
def debug(msg)
|
100
|
+
puts "DEBUG #{self.class.to_s} - #{msg}" if $debug
|
101
|
+
end
|
102
|
+
|
103
|
+
def out(msg)
|
104
|
+
puts msg
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Firebind -- Path Scan Client Software
|
2
|
+
# Copyright (C) 2013 Firebind Inc. All rights reserved.
|
3
|
+
# Authors - Jay Houghton
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require_relative 'tools'
|
18
|
+
require 'socket'
|
19
|
+
|
20
|
+
module Firebind
|
21
|
+
|
22
|
+
|
23
|
+
class Transport
|
24
|
+
|
25
|
+
include Tools
|
26
|
+
|
27
|
+
def initialize (echo_server, timeout)
|
28
|
+
@echo_server = echo_server
|
29
|
+
@timeout = timeout
|
30
|
+
@timeout_seconds = timeout.to_f/1000
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def close
|
35
|
+
begin
|
36
|
+
#noinspection RubyResolve
|
37
|
+
@socket.close
|
38
|
+
rescue
|
39
|
+
debug "error closing socket #{$!}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# Firebind -- Path Scan Client Software
|
2
|
+
# Copyright (C) 2013 Firebind Inc. All rights reserved.
|
3
|
+
# Authors - Jay Houghton
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
6
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
7
|
+
# the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require_relative 'transport'
|
18
|
+
require_relative 'tools'
|
19
|
+
require_relative 'scan_error'
|
20
|
+
|
21
|
+
module Firebind
|
22
|
+
|
23
|
+
# Send and receive echo using UDP transport.
|
24
|
+
class UdpTransport < Transport
|
25
|
+
include Tools
|
26
|
+
def initialize (echo_server,timeout)
|
27
|
+
super(echo_server,timeout)
|
28
|
+
end
|
29
|
+
|
30
|
+
def connect(port)
|
31
|
+
@port = port
|
32
|
+
begin
|
33
|
+
@socket = Socket.new(:INET,:DGRAM)
|
34
|
+
@remote_addr = Socket.pack_sockaddr_in(@port, @echo_server)
|
35
|
+
@socket.connect_nonblock @remote_addr
|
36
|
+
rescue
|
37
|
+
raise Firebind::ScanError.new :FAILURE_ON_PAYLOAD_SEND
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Send data to the echo server, we'll send multiple copies of the same data. So long as the echo server
|
42
|
+
# receives one of these we should expect an echo response.
|
43
|
+
#
|
44
|
+
# @param [Array[]] payload - array of bytes
|
45
|
+
def send(payload,num_payloads = 10)
|
46
|
+
@payload = payload
|
47
|
+
size = payload.length
|
48
|
+
count = 0
|
49
|
+
send_start = Time.now
|
50
|
+
|
51
|
+
# send the payload this many times
|
52
|
+
payloads_sent = 0
|
53
|
+
|
54
|
+
debug "sending buffer #{payload.to_s}"
|
55
|
+
#payload.each do |byte|
|
56
|
+
while count < size && Time.now - send_start < @timeout_seconds
|
57
|
+
begin
|
58
|
+
|
59
|
+
# chomp up the payload depending upon where we left off
|
60
|
+
send_buffer = payload[count,size-count]
|
61
|
+
|
62
|
+
count += @socket.write_nonblock send_buffer.pack('C*')
|
63
|
+
debug "wrote #{count} bytes to #{@echo_server}:#{@port}"
|
64
|
+
|
65
|
+
if payloads_sent < num_payloads and count == size
|
66
|
+
# reset counters to send another payload
|
67
|
+
debug "sending datagram number #{payloads_sent}"
|
68
|
+
count = 0
|
69
|
+
payloads_sent += 1
|
70
|
+
end
|
71
|
+
|
72
|
+
rescue IO::WaitWritable, Errno::EINTR
|
73
|
+
|
74
|
+
if count == size
|
75
|
+
# we've written our payload, we're done
|
76
|
+
debug "completed #{count} byte payload to #{@echo_server}:#{@port}"
|
77
|
+
elsif Time.now - send_start > @timeout_seconds
|
78
|
+
# we've timed out of sending
|
79
|
+
raise Firebind::ScanError.new :FAILURE_ON_PAYLOAD_SEND
|
80
|
+
else
|
81
|
+
# try to write some more ? http://ruby-doc.org/core-1.9.3/IO.html#method-i-write_nonblock
|
82
|
+
retry
|
83
|
+
end
|
84
|
+
|
85
|
+
rescue
|
86
|
+
#collect all the ways a send can fail...
|
87
|
+
raise Firebind::ScanError.new(:FAILURE_ON_PAYLOAD_SEND, $!)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# Receive echo response from server. Partway through the timeout we'll call send() again to send
|
95
|
+
# more datagrams as a last ditch effort to get data across.
|
96
|
+
#
|
97
|
+
# PAYLOAD_REFUSED_ON_RECV
|
98
|
+
# PAYLOAD_ERROR_ON_RECV
|
99
|
+
# PAYLOAD_TIMED_OUT_ON_RECV
|
100
|
+
#
|
101
|
+
def receive
|
102
|
+
size = @payload.length
|
103
|
+
count = 0
|
104
|
+
receive_start = Time.now
|
105
|
+
receive_buffer = ''
|
106
|
+
halfway = @timeout_seconds/2
|
107
|
+
|
108
|
+
while Time.now - receive_start <= @timeout_seconds && count < size
|
109
|
+
|
110
|
+
begin
|
111
|
+
read_result = @socket.read_nonblock(1024)
|
112
|
+
count += read_result.length # because appending to receive_buffer won't count recv bytes via .length
|
113
|
+
# add read_result to our buffer
|
114
|
+
receive_buffer << read_result
|
115
|
+
debug "read buffer is #{count}"
|
116
|
+
rescue IO::WaitReadable
|
117
|
+
if count == size
|
118
|
+
# we're done - read all we had expected to receive
|
119
|
+
debug "completed full read of #{count} bytes"
|
120
|
+
break
|
121
|
+
elsif Time.now - receive_start > @timeout_seconds
|
122
|
+
# timeout on receive
|
123
|
+
raise Firebind::ScanError.new(:PAYLOAD_TIMED_OUT_ON_RECV)
|
124
|
+
else
|
125
|
+
# send some more datagrams?
|
126
|
+
if Time.now - receive_start > halfway
|
127
|
+
begin
|
128
|
+
debug "halfway through timeout sending more datagrams on port #{@port}"
|
129
|
+
send(@payload,5)
|
130
|
+
rescue
|
131
|
+
debug "mid-receive cycle resend resulted in #{$!} ignoring"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# keep going
|
136
|
+
IO.select([@socket],nil,nil,halfway)
|
137
|
+
retry
|
138
|
+
end
|
139
|
+
rescue EOFError => e
|
140
|
+
# connection was forcefully dropped or sent a final FIN ACK closing sequence
|
141
|
+
if count != size
|
142
|
+
raise Firebind::ScanError.new(:PAYLOAD_ERROR_ON_RECV,e)
|
143
|
+
end
|
144
|
+
|
145
|
+
rescue
|
146
|
+
#collect all the ways a receive can fail... todo:
|
147
|
+
if $debug
|
148
|
+
puts 'Caught unhandled socket error:'
|
149
|
+
p $1
|
150
|
+
puts $@
|
151
|
+
end
|
152
|
+
raise Firebind::ScanError.new(:PAYLOAD_REFUSED_ON_RECV,$!)
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
# we still could have timed out here, check rx buffer size first
|
158
|
+
if count < size
|
159
|
+
raise Firebind::ScanError.new(:PAYLOAD_TIMED_OUT_ON_RECV)
|
160
|
+
end
|
161
|
+
|
162
|
+
# now compare what we received with what we sent
|
163
|
+
received_array = receive_buffer.unpack('C*')
|
164
|
+
if received_array != @payload
|
165
|
+
debug "rx buffer #{received_array.to_s} is NOT equal to expected #{@payload.to_s}"
|
166
|
+
raise Firebind::ScanError.new(:PAYLOAD_MISMATCH_ON_RECV)
|
167
|
+
else
|
168
|
+
debug "rx buffer #{received_array.to_s} is equal to payload #{@payload.to_s}"
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|