Ruby - See if a port is open - ruby

I need a quick way to find out if a given port is open with Ruby. I currently am fiddling around with this:
require 'socket'
def is_port_open?(ip, port)
begin
TCPSocket.new(ip, port)
rescue Errno::ECONNREFUSED
return false
end
return true
end
It works great if the port is open, but the downside of this is that occasionally it will just sit and wait for 10-20 seconds and then eventually time out, throwing a ETIMEOUT exception (if the port is closed). My question is thus:
Can this code be amended to only wait for a second (and return false if we get nothing back by then) or is there a better way to check if a given port is open on a given host?
Edit: Calling bash code is acceptable also as long as it works cross-platform (e.g., Mac OS X, *nix, and Cygwin), although I do prefer Ruby code.

Something like the following might work:
require 'socket'
require 'timeout'
def is_port_open?(ip, port)
begin
Timeout::timeout(1) do
begin
s = TCPSocket.new(ip, port)
s.close
return true
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
return false
end
end
rescue Timeout::Error
end
return false
end

All other existing answer are undesirable. Using Timeout is discouraged. Perhaps things depend on ruby version. At least since 2.0 one can simply use:
Socket.tcp("www.ruby-lang.org", 10567, connect_timeout: 5) {}
For older ruby the best method I could find is using non-blocking mode and then select. Described here:
https://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/

More Ruby idiomatic syntax:
require 'socket'
require 'timeout'
def port_open?(ip, port, seconds=1)
Timeout::timeout(seconds) do
begin
TCPSocket.new(ip, port).close
true
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
false
end
end
rescue Timeout::Error
false
end

I recently came up with this solution, making use of the unix lsof command:
def port_open?(port)
!system("lsof -i:#{port}", out: '/dev/null')
end

Just for completeness, the Bash would be something like this:
$ netcat $HOST $PORT -w 1 -q 0 </dev/null && do_something
-w 1 specifies a timeout of 1 second, and -q 0 says that, when connected, close the connection as soon as stdin gives EOF (which /dev/null will do straight away).
Bash also has its own built-in TCP/UDP services, but they are a compile-time option and I don't have a Bash compiled with them :P

My slight variation to Chris Rice's answer. Still handles timing out on a single attempt but also allows multiple retries until you give up.
def is_port_open?(host, port, timeout, sleep_period)
begin
Timeout::timeout(timeout) do
begin
s = TCPSocket.new(host, port)
s.close
return true
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
sleep(sleep_period)
retry
end
end
rescue Timeout::Error
return false
end
end

All *nix platforms:
try nc / netcat command as follow.
`nc -z -w #{timeout_in_seconds} -G #{timeout_in_seconds} #{host} #{port}`
if $?.exitstatus == 0
#port is open
else
#refused, port is closed
end
The -z flag can be used to tell nc to report open ports, rather than initiate a connection.
The -w flag means Timeout for connects and final net reads
The -G flag is connection timeout in seconds
Use -n flag to work with IP address rather than hostname.
Examples:
# `nc -z -w 1 -G 1 google.com 80`
# `nc -z -w 1 -G 1 -n 123.234.1.18 80`

My solution is derived from the posted solutions.
require 'socket'
def is_port_open?(ip, port)
begin
s = Socket.tcp(ip, port, connect_timeout: 5)
s.close
return true
rescue => e
# possible exceptions:
# - Errno::ECONNREFUSED
# - Errno::EHOSTUNREACH
# - Errno::ETIMEDOUT
puts "#{e.class}: #{e.message}"
return false
end
end

Related

How to test SSH connection using Ruby

This is what I got so far. This works great, the problem being I can't input a password for the ssh login, I need to have shared ssh keys in order for this to work:
def ssh_conn(user, host, &block)
begin
ping_output = []
timeout(20) do
ping_output = IO.popen("ssh #{user}##{host} 'echo \"success\"'", "w+")
end
ping = ping_output.readlines.join[/success/] ? true : false
rescue Timeout::Error
ping = false
rescue
ping = false
end
ping_output.close
if block_given? && ping
yield
end
return ping
end
The question here is: How can I do something similar to this, but with password input through the arguments passed to the method? Preferably using ruby native Classes/Methods without installing any "external" gems.
By searching a bit in StackOverflow I've found this thread
and I was able to solve my problem doing this:
def ssh_try(user, host, pass)
puts "SSHing #{host} ..."
Net::SSH.start( host.to_s, user.to_s, :password => pass.to_s ) do |ssh|
puts ssh.exec!('date')
puts "Logging out..."
end
end
Anyone who is facing a similar problem can try this method, works great to test/use ssh connection in ruby.
I believe you cannot do that with ssh itself, but that's what sshpass it's for, as you can read in this serverfault answer. In Ubuntu:
$ sudo apt-get install sshpass
And then change your IO call like this:
ping_output = IO.popen("sshpass -p ssh #{user}##{host} 'echo \"success\"'", "w+")
An alternative would be to rewrite your code to use Ruby SSH client, such as net-ssh, instead of using the system command. This is actually my recommendation, since it'll allow you to work at a higher abstraction level and not deal with system issues. Also, the result looks more beautiful! Check this (untested) code:
require 'net/ssh'
def ssh_conn(user, host, password, &block)
authentication_successful = Net::SSH::Authentication::Session.authenticate(host, user, password)
authentication_successful && (yield if block_given?)
authentication_successful
end

Ruby IO from a service at port 6557 in Sinatra

I have to take a dump of a service in sinatra and display it in the content area of the webpage.
The Service I have to access via code runs on server at port 6557. It doesnt use any encryption or authentication. Its a plain readonly request response thingy like http.
Here is what works in teminal
$ echo "GET hosts" | nc 192.168.1.1 6557
gives me the intended output. I need to do something similar using the sinatra application.
I wrote this code but is grossly incorrect. Can sombody help me with code or lookup materials or examples.
get '/' do
host = "192.168.1.1"
port = 6557
dat = ""
#socket = TCPSocket.open (host, port)
while(true)
if(IO.select([],[],[#socket],0))
socket.close
return
end
begin
while( (data = #socket.recv_nonblock(100)) != "")
dat = dat+ data
end
rescue Errno::EAGAIN
end
begin
#str = "GET hosts"
#socket.puts(#str);
rescue Errno::EAGAIN
rescue EOFError
exit
end
IO.select([#socket], [#socket], [#socket])
end
#line = dat
erb :info
end
The code on execution just hangs up.
Also if possible please give some links to read up to get a conceptual context of the problem.
I think the Ruby equivalent to your shell command should be as simple as:
require "socket"
socket = TCPSocket.new "192.168.1.1", 6557
socket.puts "GET hosts"
socket.read
According to the docs, #read should close the socket automatically, so you don't need to worry about doing that manually.
You can execute shell commands directly from ruby using backticks or the system command. Something like this may work for you:
get "/" do
#line = `echo "GET hosts" | nc 192.168.1.1 6557`
erb :info
end
Check out the ruby docs for Kernel#system for more info.

How to execute interactive shell program on a remote host from ruby

I am trying to execute an interactive shell program on a remote host from another ruby program. For the sake of simplicity let's suppose that the program I want to execute is something like this:
puts "Give me a number:"
number = gets.chomp()
puts "You gave me #{number}"
The approach that most successful has been so far is using the one I got from here. It is this one:
require 'open3'
Open3.popen3("ssh -tt root#remote 'ruby numbers.rb'") do |stdin, stdout, stderr|
# stdin = input stream
# stdout = output stream
# stderr = stderr stream
threads = []
threads << Thread.new(stderr) do |terr|
while (line = terr.gets)
puts "stderr: #{line}"
end
end
threads << Thread.new(stdout) do |terr|
while (line = terr.gets)
puts "stdout: #{line}"
end
end
sleep(2)
puts "Give me an answer: "
answer = gets.chomp()
stdin.puts answer
threads.each{|t| t.join()} #in order to cleanup when you're done.
end
The problem is that this is not "interactive" enough to me, and the program that I would like to execute (not the simple numbers.rb) has a lot more of input / output. You can think of it as an apt-get install that will ask you for some input to solve some problems.
I have read about net::ssh and pty, but couldn't see if they were going to be the (easy/elegant) solution I am looking for.
The ideal solution will be to make it in such a way that the user does not realize that the IO is being done on a remote host: the stdin goes to the remote host stdin, the stdout from the remote host comes to me and I show it.
If you have any ideas I could try I will be happy to hear them. Thank you!
Try this:
require "readline"
require 'open3'
Open3.popen3("ssh -tt root#remote 'ruby numbers.rb'") do |i, o, e, th|
Thread.new {
while !i.closed? do
input =Readline.readline("", true).strip
i.puts input
end
}
t_err = Thread.new {
while !e.eof? do
putc e.readchar
end
}
t_out = Thread.new {
while !o.eof? do
putc o.readchar
end
}
Process::waitpid(th.pid) rescue nil
# "rescue nil" is there in case process already ended.
t_err.join
t_out.join
end
I got it working, but don't ask me why it works. It was mainly trial/error.
Alternatives:
Using Net::SSH, you need to use :on_process and a Thread: ruby net/ssh channel dies? Don't forget to add session.loop(0.1). More info at the link. The Thread/:on_process idea inspired me to write a gem for my own use: https://github.com/da99/Chee/blob/master/lib/Chee.rb
If the last call in your Ruby program is SSH, then you can exec ssh -tt root#remote 'ruby numbers.rb'. But, if you still want interactivity between User<->Ruby<->SSH, then the previous alternative is the best.

Socket in Ruby blindly hangs when trying to check an offline server

I use the following code to check the server status of a certain game server to see if the game server is online.
begin
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(game_server.gameserver_port, game_server.gameserver_hostname)
sock.connect(sockaddr)
server_status.status = 1
rescue
server_status.status = 0
end
However it seems that the code blindly hangs up on the line without proceeding anywhere sock.connect(sockaddr) and does not throw an error when there's no services listening on that port. Is there a better way to do this in Ruby?
Could timeout be a good solution?
require 'timeout'
begin
timeout(5) do
# socket stuff...
end
rescue Timeout::Error
puts "Timed out!"
end

Recovering from a broken TCP socket in Ruby when in gets()

I'm reading lines of input on a TCP socket, similar to this:
class Bla
def getcmd
#sock.gets unless #sock.closed?
end
def start
srv = TCPServer.new(5000)
#sock = srv.accept
while ! #sock.closed?
ans = getcmd
end
end
end
If the endpoint terminates the connection while getline() is running then gets() hangs.
How can I work around this? Is it necessary to do non-blocking or timed I/O?
You can use select to see whether you can safely gets from the socket, see following implementation of a TCPServer using this technique.
require 'socket'
host, port = 'localhost', 7000
TCPServer.open(host, port) do |server|
while client = server.accept
readfds = true
got = nil
begin
readfds, writefds, exceptfds = select([client], nil, nil, 0.1)
p :r => readfds, :w => writefds, :e => exceptfds
if readfds
got = client.gets
p got
end
end while got
end
end
And here a client that tries to break the server:
require 'socket'
host, port = 'localhost', 7000
TCPSocket.open(host, port) do |socket|
socket.puts "Hey there"
socket.write 'he'
socket.flush
socket.close
end
The IO#closed? returns true when both reader and writer are closed.
In your case, the #sock.gets returns nil, and then you call the getcmd again, and this runs in a never ending loop. You can either use select, or close the socket when gets returns nil.
I recommend using readpartial to read from your socket and also catching peer resets:
while true
sockets_ready = select(#sockets, nil, nil, nil)
if sockets_ready != nil
sockets_ready[0].each do |socket|
begin
if (socket == #server_socket)
# puts "Connection accepted!"
#sockets << #server_socket.accept
else
# Received something on a client socket
if socket.eof?
# puts "Disconnect!"
socket.close
#sockets.delete(socket)
else
data = ""
recv_length = 256
while (tmp = socket.readpartial(recv_length))
data += tmp
break if (!socket.ready?)
end
listen socket, data
end
end
rescue Exception => exception
case exception
when Errno::ECONNRESET,Errno::ECONNABORTED,Errno::ETIMEDOUT
# puts "Socket: #{exception.class}"
#sockets.delete(socket)
else
raise exception
end
end
end
end
end
This code borrows heavily from some nice IBM code by M. Tim Jones. Note that #server_socket is initialized by:
#server_socket = TCPServer.open(port)
#sockets is just an array of sockets.
I simply pgrep "ruby" to find the pid, and kill -9 the pid and restart.
If you believe the rdoc for ruby sockets, they don't implement gets. This leads me to believe gets is being provided by a higher level of abstraction (maybe the IO libraries?) and probably isn't aware of socket-specific things like 'connection closed.'
Try using recvfrom instead of gets

Resources