I've been tasked with modifying an existing Ruby script, but my Ruby knowledge is basic at best...
I need to add a method to check if a server's port is open. if it is, the script should resume doing whatever it's doing. if not, it should exit.
I've applied the following method, taken from Ruby - See if a port is open:
def is_port_open?
#host = "localhost"
#port = "8080"
begin
Timeout::timeout(1) do
begin
s = TCPSocket.new(#host, #port)
s.close
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
return "port closed :("
end
end
rescue Timeout::Error
end
return "problem with timeout?"
end
This method seems to work well, except when returning "nil" if the port is open. How do i suppress any output (unless there is an error)?
Thanks in Advance!
Whether you only need to check for a condition (port is open):
require 'timeout'
require 'socket'
def is_port_open? host, port
#host = host || "localhost"
#port = port || "8080"
begin
Timeout::timeout(1) do
begin
s = TCPSocket.new(#host, #port)
s.close
return true # success
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
return false # socket error
end
end
rescue Timeout::Error
end
return false # timeout error
end
is_port_open? 'localhost', 8080
#⇒ true
is_port_open? 'localhost', 11111
#⇒ false
It’s now up to you what to return in case of error etc. Please note, that another option would be to let exceptions propagate to caller. This function would be a way shorter, but you’ll need to handle exceptions in caller.
Related
Intro
I have a client that makes numerous SSL connections to a 3rd party service. In certain cases, the 3rd party stops responding during the socket and ssl negotiation process. When this occurs, my current implementation "sits" for hours on end before timing out.
To combat this, I'm trying to implement the following process:
require 'socket'
require 'openssl'
# variables
host = '....'
port = ...
p12 = #OpenSSL::PKCS12 object
# set up socket
addr = Socket.getaddrinfo(host, nil)
sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])
socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
begin
socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
if IO.select(nil, [socket], nil, timeout)
begin
socket.connect_nonblock(sockaddr)
rescue Errno::EISCONN
puts "socket connected"
rescue
puts "socket error"
socket.close
raise
end
else
socket.close
raise "Connection timeout"
end
end
# negotiate ssl
context = OpenSSL::SSL::SSLContext.new
context.cert = p12.certificate
context.key = p12.key
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
ssl_socket.sync_close = true
puts "ssl connecting"
ssl_socket.connect_nonblock
puts "ssl connected"
# cleanup
ssl_socket.close
puts "socket closed"
ssl_socket.connect_nonblock will eventually be wrapped in a similar structure as socket.connect_nonblock is.
The Problem
The issue I'm running into is that ssl_socket.connect_nonblock raises the following when run:
`connect_nonblock': read would block (OpenSSL::SSL::SSLError)
Instead, I'd expect it to raise an IO::WaitWritable as socket.connect_nonblock does.
I've scoured the internet for information on this particular error but can't find anything of particular use. From what I gather, others have had success using this method, so I'm not sure what I'm missing. For the sake of completeness, I've found the same results with both ruby 2.2.0 and 1.9.3.
Any suggestions are greatly appreciated!
Have same problem, I tried below, it seems works right for my situation.
ssl_socket = OpenSSL::SSL::SSLSocket.new socket, context
ssl_socket.sync = true
begin
ssl_socket.connect_nonblock
rescue IO::WaitReadable
if IO.select([ssl_socket], nil, nil, timeout)
retry
else
# timeout
end
rescue IO::WaitWritable
if IO.select(nil, [ssl_socket], nil, timeout)
retry
else
# timeout
end
end
I have been searching the whole day for socket accept non blocking. I found recv non blocking but that wouldn't benefit me in anyway. My script first starts a new socket class. It binds to the client with ip 127.0.0.1 and port 6112. Then it starts multi threading. Multi threading takes #sock.accept. << That is blocking. I have then used accept_nonblock. Though, that would throw me the following error:
IO::EWOULDBLOCKWaitReadable : A non-blocking socket operation could not be completed immediately. - accept(2) would block
I am using Ruby 2.2.
NOTE: I do not intend to use Rails to solve my problem, or give me a shortcut. I am sticking with pure Ruby 2.2.
Here is my script:
require 'socket'
include Socket::Constants
#sock = Socket.new(AF_INET, SOCK_STREAM, 0)
#sockaddr = Socket.sockaddr_in(6112, '127.0.0.1')
#sock.bind(#sockaddr)
#sock.listen(5)
Thread.new(#sock.accept_nonblock) do |connection|
#client = Client.new(ip, connection, self)
#clients.push(#client)
begin
while connection
packet = connection.recv(55555)
if packet == nil
DeleteClient(connection)
else
#toput = "[RECV]: #{packet}"
puts #toput
end
end
rescue Exception => e
if e.class != IOError
line1 = e.backtrace[0].split(".rb").last
line = line1.split(":")[1]
#Log.Error(e, e.class, e.backtrace[0].split(".rb").first + ".rb",line)
puts "#{ e } (#{ e.class })"
end
end
def DeleteClient(connection)
#clients.delete(#client)
connection.close
end
http://docs.ruby-lang.org/en/2.2.0/Socket.html#method-i-accept_nonblock
accept_nonblock raises an exception when it can't immediately accept a connection. You are expected to rescue this exception and then IO.select the socket.
begin # emulate blocking accept
client_socket, client_addrinfo = socket.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
IO.select([socket])
retry
end
A patch has recently been accepted which will add an exception: false option to accept_nonblock, which will allow you to use it without using exceptions for flow control. I don't know that it's shipped yet, though.
I'm going on a limb here, and posting a large chunk of code.
I hope it will answer both your question and the any related questions others reading this answer might raise.
I'm sorry if I went overboard, I just thought it was almost all relevant.
Issues like looping through an event stack, using IO.select to push events in a non-block manner and other performance issues are all related (in my opinion) to the nonblocking concept of socket programming.
So i'm posting a ruby module which acts as a server with a reactor, using a limited number of threads, rather than thousands of threads, each per connection (12 threads will give you better performance than a hundred). The reactor utilizes the IO.select method with a timeout once all it's active events are handled.
The module can set up multiple listening sockets which use #accept_nonblock, and they all currently act as an echo server.
It's basically the same code I used for the Plezi framework's core... with some stripped down functionality.
The following is a thread-pool with 12 working threads + the main thread (which will sleep and wait for the "TERM" signal)...
...And it's an example of an accept_nonblock with exception handling and a thread pool.
It's a simple socket echo server, test it as a remote client using telnet:
> telnet localhost 3000
Hi!
# => Hi!
bye
#=> will disconnect
here's the code - Good Luck!!!
require 'socket'
module SmallServer
module_function
####
# Replace this method with your actual server logic.
#
# this code will be called when a socket recieves data.
#
# For now, we will just echo.
def got_data io, io_params
begin
got = io.recv_nonblock( 1048576 ) # with maximum number of bytes to read at a time...
puts "echoing: #{got}"
if got.match /^(exit|bye|q)\R/
puts 'closing connection.'
io.puts "bye bye!"
remove_connection io
else
io.puts "echoing: #{got}"
end
rescue => e
# should also log error
remove_connection io
end
end
#########
# main loop and activation code
#
# This will create a thread pool and set them running.
def start
# prepare threads
exit_flag = false
max_threads = 12
threads = []
thread_cycle = Proc.new do
io_review rescue false
true while fire_event
end
(max_threads).times { Thread.new { thread_cycle.call until exit_flag } }
# set signal tarps
trap('INT'){ exit_flag = true; raise "close!" }
trap('TERM'){ exit_flag = true; raise "close!" }
puts "Services running. Press ^C to stop"
# sleep until trap raises exception (cycling might cause the main thread to loose signals that might be caught inside rescue clauses)
(sleep unless SERVICES.empty?) rescue true
# start shutdown.
exit_flag = true
# set fallback tarps
trap('INT'){ puts 'Forced exit.'; Kernel.exit }
trap('TERM'){ puts 'Forced exit.'; Kernel.exit }
puts 'Started shutdown process. Press ^C to force quit.'
# shut down listening sockets
stop_services
# disconnect active connections
stop_connections
# cycle down threads
puts "Waiting for workers to cycle down"
threads.each {|t| t.join if t.alive?}
# rundown any active events
thread_cycle.call
end
#######################
## Events (Callbacks) / Multi-tasking Platform
EVENTS = []
E_LOCKER = Mutex.new
# returns true if there are any unhandled events
def events?
E_LOCKER.synchronize {!EVENTS.empty?}
end
# pushes an event to the event's stack
# if a block is passed along, it will be used as a callback: the block will be called with the values returned by the handler's `call` method.
def push_event handler, *args, &block
if block
E_LOCKER.synchronize {EVENTS << [(Proc.new {|a| push_event block, handler.call(*a)} ), args]}
else
E_LOCKER.synchronize {EVENTS << [handler, args]}
end
end
# Runs the block asynchronously by pushing it as an event to the event's stack
#
def run_async *args, &block
E_LOCKER.synchronize {EVENTS << [ block, args ]} if block
!block.nil?
end
# creates an asynchronous call to a method, with an optional callback (shortcut)
def callback object, method, *args, &block
push_event object.method(method), *args, &block
end
# event handling FIFO
def fire_event
event = E_LOCKER.synchronize {EVENTS.shift}
return false unless event
begin
event[0].call(*event[1])
rescue OpenSSL::SSL::SSLError => e
puts "SSL Bump - SSL Certificate refused?"
rescue Exception => e
raise if e.is_a?(SignalException) || e.is_a?(SystemExit)
error e
end
true
end
#####
# Reactor
#
# IO review code will review the connections and sockets
# it will accept new connections and react to socket input
IO_LOCKER = Mutex.new
def io_review
IO_LOCKER.synchronize do
return false unless EVENTS.empty?
united = SERVICES.keys + IO_CONNECTION_DIC.keys
return false if united.empty?
io_r = (IO.select(united, nil, united, 0.1) )
if io_r
io_r[0].each do |io|
if SERVICES[io]
begin
callback self, :add_connection, io.accept_nonblock, SERVICES[io]
rescue Errno::EWOULDBLOCK => e
rescue => e
# log
end
elsif IO_CONNECTION_DIC[io]
callback(self, :got_data, io, IO_CONNECTION_DIC[io] )
else
puts "what?!"
remove_connection(io)
SERVICES.delete(io)
end
end
io_r[2].each { |io| (remove_connection(io) || SERVICES.delete(io)).close rescue true }
end
end
callback self, :clear_connections
true
end
#######################
# IO - listening sockets (services)
SERVICES = {}
S_LOCKER = Mutex.new
def add_service port = 3000, parameters = {}
parameters[:port] ||= port
parameters.update port if port.is_a?(Hash)
service = TCPServer.new(parameters[:port])
S_LOCKER.synchronize {SERVICES[service] = parameters}
callback Kernel, :puts, "Started listening on port #{port}."
true
end
def stop_services
puts 'Stopping services'
S_LOCKER.synchronize {SERVICES.each {|s, p| (s.close rescue true); puts "Stoped listening on port #{p[:port]}"}; SERVICES.clear }
end
#####################
# IO - Active connections handling
IO_CONNECTION_DIC = {}
C_LOCKER = Mutex.new
def stop_connections
C_LOCKER.synchronize {IO_CONNECTION_DIC.each {|io, params| io.close rescue true} ; IO_CONNECTION_DIC.clear}
end
def add_connection io, more_data
C_LOCKER.synchronize {IO_CONNECTION_DIC[io] = more_data} if io
end
def remove_connection io
C_LOCKER.synchronize { IO_CONNECTION_DIC.delete io; io.close rescue true }
end
# clears closed connections from the stack
def clear_connections
C_LOCKER.synchronize { IO_CONNECTION_DIC.delete_if {|c| c.closed? } }
end
end
start the echo server in irb with:
SmallServer.add_service(3000) ; SmallServer.start
I am attempting to create a better port scanner by implementing more error handling, and I've run into a bit of trouble.
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
rescue (Errno::ECONNREFUSED)
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
end
end
def run
port = 1
begin
while port <= ARGV[1].to_i do
popen(ARGV[0], port)
port += 1
end
rescue (Errno::EHOSTUNREACH)
puts "No path to host"
rescue(Interrupt)
puts " Process interrupted"
end
end
run
The trouble is that for each port scanned, it will print "No path to host", instead of printing that once and then closing the socket. I'm clearly doing it wrong but everywhere online I find simple code closes a socket connection this way.
I think that you forgot to close the if statement. Also, use 'ensure' to close it.
Try this:
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
end
rescue (Errno::ECONNREFUSED)
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
rescue (Errno::EHOSTUNREACH)
puts "No path to host"
ensure
sock.close
end
end
Like you mentioned in your comments and your code changes, you would have to move the socket closing into the block that's calling your code rather than inside of that code itself, since the block of code will continuously call the method and the socket will never actually be closed.
You'll basically have to do what you posted in your code:
require 'socket'
def pscan(host_name, port)
begin
sock = Socket.new(:INET, :STREAM)
raw = Socket.sockaddr_in(port, host_name)
if sock.connect(raw)
puts "Port #{port} is up!"
end
rescue (Errno::ECONNREFUSED)
puts "Connection refused to port #{port}"
false
rescue (Errno::ETIMEDOUT)
puts "Port #{port} timed out!"
false
end
end
def run
puts "Attempting to scan host #{ARGV[0]}"
port = 1
begin
while port <= ARGV[1].to_i do
pscan(ARGV[0], port)
port += 1
end
rescue (Errno::EHOSTUNREACH)
puts "No path to host #{ARVG[0]}"
rescue(Interrupt)
puts "Process interrupted"
end
end
run
Some thoughts: you may not always get "No path to host" errors when running your code; when I tested it in my network, I never got a "No path to host" error but always got a "Port ... timed out!" error even when I tested it against known unreachable hosts such as 10.0.0.1. You'll have to be wary of that when you run your code in other networks as you might get different results.
$ irb
1.9.3-p448 :001 > require 'socket'
=> true
1.9.3-p448 :002 > TCPSocket.new('www.example.com', 111)
gives
Errno::ETIMEDOUT: Operation timed out - connect(2)
Questions:
How can I define the timeout value for TCPSocket.new?
How can I properly catch the timeout (or, in general, socket) exception(s)?
At least since 2.0 one can simply use Socket::tcp:
Socket.tcp("www.ruby-lang.org", 10567, connect_timeout: 5) {}
Note the block at the end of the expression, which is used to get connection closed in case such is established.
For older versions #falstru answer appears to be best.
Use begin .. rescue Errno::ETIMEDOUT to catch the timeout:
require 'socket'
begin
TCPSocket.new('www.example.com', 111)
rescue Errno::ETIMEDOUT
p 'timeout'
end
To catch any socket exceptions, use SystemCallError instead.
According to the SystemCallError documentation:
SystemCallError is the base class for all low-level platform-dependent errors.
The errors available on the current platform are subclasses of
SystemCallError and are defined in the Errno module.
TCPSocket.new does not support timeout directly.
Use Socket::connect_non_blocking and IO::select to set timeout.
require 'socket'
def connect(host, port, timeout = 5)
# Convert the passed host into structures the non-blocking calls
# can deal with
addr = Socket.getaddrinfo(host, nil)
sockaddr = Socket.pack_sockaddr_in(port, addr[0][4])
Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
begin
# Initiate the socket connection in the background. If it doesn't fail
# immediatelyit will raise an IO::WaitWritable (Errno::EINPROGRESS)
# indicating the connection is in progress.
socket.connect_nonblock(sockaddr)
rescue IO::WaitWritable
# IO.select will block until the socket is writable or the timeout
# is exceeded - whichever comes first.
if IO.select(nil, [socket], nil, timeout)
begin
# Verify there is now a good connection
socket.connect_nonblock(sockaddr)
rescue Errno::EISCONN
# Good news everybody, the socket is connected!
rescue
# An unexpected exception was raised - the connection is no good.
socket.close
raise
end
else
# IO.select returns nil when the socket is not ready before timeout
# seconds have elapsed
socket.close
raise "Connection timeout"
end
end
end
end
connect('www.example.com', 111, 2)
The above code comes from "Setting a Socket Connection Timeout in Ruby".
If you like the idea of avoiding the pitfalls of Timeout, but prefer to avoid having to deal with your own implementation of the *_nonblock+select implementation, you can use the tcp_timeout gem.
The tcp_timeout gem monkey-patches TCPSocket#connect, #read, and #write so that they use non-blocking I/O and have timeouts that you can enable.
You to make a timeout you can use ruby's Timeout module:
reqiure 'socket'
reqiure 'timeout'
begin
Timeout.timeout(10) do
begin
TCPSocket.new('www.example.com', 111)
rescue Errno::ENETUNREACH
retry # or do something on network timeout
end
end
rescue Timeout::Error
puts "timed out"
# do something on timeout
end
and you'll get after 10 seconds:
# timed out
# => nil
NOTE: Some people may think that it is dangerous solution, well, this opinion has right to exist, but there were no real investigations proceeded, so, that opinion is just a hypothesis. And currently it is better to use internal ruby's timeout engine in Socket class like the following:
Socket.tcp("www.ruby-lang.org", 80, connect_timeout: 80) do |sock|
...
end
My question is related to How to rescue timeout issues (Ruby, Rails).
Here's the common way to rescue from a timeout:
def action
# Post using Net::HTTP
rescue Timeout::Error => e
# Do something
end
I'd like to determine if the exception was raised while trying to connect to the host, or if it was raised while trying to read from the host. Is this possible?
Here's the solution (after Ben's fix):
require "net/http"
http = Net::HTTP.new("example.com")
http.open_timeout = 2
http.read_timeout = 3
begin
http.start
begin
http.request_get("/whatever?") do |res|
res.read_body
end
rescue Timeout::Error
puts "Timeout due to reading"
end
rescue Timeout::Error
puts "Timeout due to connecting"
end
Marc-André Lafortune's solution is still the best if you can't upgrade to ruby 2.x.
Starting from 2.x, a subclass of Timeout::Error will be raised depending on which timeout was triggered:
Net::OpenTimeout
Net::ReadTimeout
However, the read_timeout behavior is strange on 2.x, because it seems to double the value you set. This article explains why.
Here's a test for both timeouts (tested on 1.8.7, 1.9.3, 2.1.2, 2.2.4).
EDIT: The open_timeout test works on Mac, but on Linux, the client gets a "connection refused" error.
require "net/http"
require "socket"
SERVER_HOST = '127.0.0.1'
SERVER_PORT = 9999
def main
puts 'with_nonlistening_server'
with_nonlistening_server do
make_request
end
puts
puts 'with_listening_server'
with_listening_server do
make_request
end
end
def with_listening_server
# This automatically starts listening
serv = TCPServer.new(SERVER_HOST, SERVER_PORT)
begin
yield
ensure
serv.close
end
end
def with_nonlistening_server
raw_serv = Socket.new Socket::AF_INET, Socket::SOCK_STREAM, 0
addr = Socket.pack_sockaddr_in SERVER_PORT, SERVER_HOST
# Bind, but don't listen
raw_serv.bind addr
begin
yield
ensure
raw_serv.close
end
end
def make_request
http = Net::HTTP.new(SERVER_HOST, SERVER_PORT)
http.open_timeout = 1
http.read_timeout = 1 # seems to be doubled on ruby 2.x
start_tm = Time.now
begin
http.start
begin
http.get('/')
rescue Timeout::Error => err
puts "Read timeout: #{err.inspect}"
end
rescue Timeout::Error => err
puts "Open timeout: #{err.inspect}"
end
end_tm = Time.now
puts "Duration (sec): #{end_tm - start_tm}"
end
if __FILE__ == $PROGRAM_NAME
main
end
Example output on 1.9.3:
with_nonlistening_server
Open timeout: #<Timeout::Error: execution expired>
Duration (sec): 1.002477
with_listening_server
Read timeout: #<Timeout::Error: Timeout::Error>
Duration (sec): 1.00599
Example output on 2.1.2:
with_nonlistening_server
Open timeout: #<Net::OpenTimeout: execution expired>
Duration (sec): 1.005923
with_listening_server
Read timeout: #<Net::ReadTimeout: Net::ReadTimeout>
Duration (sec): 2.009582