Closing socket doesn't work in Ruby - ruby

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.

Related

EventMachine reconnection issues

I am fairly new to programming in general and I am using EventMachine on both the client and server side to open a websocket connection between them.
My issue is with the Client side, and when the connection is lost due to network connectivity issues.
def websocket_connection
EM.run do
begin
puts "Connecting"
ws = WebSocket::EventMachine::Client.connect(:uri => "Server Info")
puts "Success WS: #{ws}"
rescue
puts "I've been rescued"
puts "Rescue WS: #{ws}"
ws = nil
sleep 10
websocket_connection
end
ws.onopen do
puts "Connected!"
end
ws.onping do
put "Ping!"
end
ws.onmessage do |msg|
puts msg
end
ws.onclose do |code|
puts "Connection Closed!"
puts "Code: #{code}"
ws = nil
sleep 10
websocket_connection
end
end
end
This connects to the server just fine, but if I pull the network connection and plug it back in I am stuck in an infinite loop of it trying to reconnect with code 1002 (WebSocket protocol error.).
I have tried to call EM.reconnect(server, port, ws) on close, but it crashes and throws this error `connect_server': unable to resolve address: The requested name is valid, but no data of the requested type was found. Which makes sense because it can't contact DNS. Even if wrap the EM.reconnect in a begin rescue it just tries once and never tries again.
I have tried stopping EventMachine and close (EM.stop) but that gets stuck in an infinite loop trying to reconnect.
I am not really sure how to get this client to reconnect to the server after a network lose.
EDIT:
Updated the code above a little bit.
CMD Line:
Success WS: #WebSocket::EventMachine::Client:0x00000002909ac8
Pulled Ethernet Cable
Rescue WS:
Connected Ethernet Cable
Success WS: #WebSocket::EventMachine::Client:0x000000031c42a8
Success WS: #WebSocket::EventMachine::Client:0x000000031a3d50
Success WS: #WebSocket::EventMachine::Client:0x00000003198a90
CTRL + C
block in websocket_connection': undefined methodonopen' for nil:NilClass (NoMethodError)
So it looks like it thinks its connecting, I don't see any connections on the server side.
Well, I couldn't find a way to do a proper reconnect using EventMachine. It looks like weird things happen in EventMachine when you drop your network connection. I ended up relaunching the ruby app under a new Process then killing the current script, not the best way to do this but after a week of trying to get the reconnect working through EventMachine I have just given up. This code works below.
def websocket_restart
exec "ruby script"
exit 0
end
def websocket_connection
EM.run do
begin
puts "Connecting"
ws = WebSocket::EventMachine::Client.connect(:uri => "Server Info")
rescue
websocket_restart
end
ws.onopen do
puts "Connected!"
end
ws.onping do
put "Ping!"
end
ws.onmessage do |msg|
puts msg
end
ws.onclose do |code|
puts "Connection Closed!"
puts "Code: #{code}"
websocket_restart
end
end
end

Non-blocking SSL socket negotiation in Ruby. Possible?

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

Suppress nil response from Ruby method

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.

TCP server using Ruby socket works but not using eventmachine

I wrote a TCP server using below code. This is to receive GPS location data via GSM network from a remote GPS sensor.
require 'socket'
server = TCPServer.open(2000) # Listen on port 2000
sockets = [server] # An array of sockets we'll monitor
log = STDOUT # Send log messages to standard out
while true
ready = select(sockets)
readable = ready[0]
readable.each do |socket|
if socket == server
client = server.accept
sockets << client
log.puts "Accepted connection from #{client.peeraddr[2]}"
while msg = client.gets
puts msg
end
else
input = socket.gets
if !input
log.puts "Client on #{socket.peeraddr[2]} disconnected"
sockets.delete(socket)
socket.close
next
end
input.chop!
if (input == "quit")
socket.puts("Bye");
log.puts "Closing connnection to #{socket.peeraddr[2]}"
sockets.delete(socket)
socket.close
else
socket.puts(input.reverse)
end
end
end
end
and then I wrote one using Eventmachine. Code as below:
require 'eventmachine'
module EchoServer
def post_init
puts "-- someone connected to the echo server!"
end
def receive_data data
puts data
end
def unbind
puts "-- someone disconnected from the echo server!"
end
end
EventMachine::run {
EventMachine::start_server "127.0.0.1", 2000, EchoServer
}
However, this eventmachine code will not receive nor display the data. Any part of the Eventmachine code that is wrong?
Thanks
I think your problem is that you are listening on localhost only, try this:
EM::run do
EM.start_server "0.0.0.0", 2000, EchoServer
end

stream closed (IOError) when closing Ruby TCPSocket client

I've got a Ruby TCPSocket client that works great except when I'm trying to close it. When I call the disconnect method in my code below, I get this error:
./smartlinc.rb:70:in `start_listen': stream closed (IOError)
from ./smartlinc.rb:132:in `initialize'
from ./smartlinc.rb:132:in `new'
from ./smartlinc.rb:132:in `start_listen'
from bot.rb:45:in `initialize'
from bot.rb:223:in `new'
from bot.rb:223
Here's the (simplified) code:
class Smartlinc
def initialize
#socket = TCPSocket.new(HOST, PORT)
end
def disconnect
#socket.close
end
def start_listen
# Listen on a background thread
th = Thread.new do
Thread.current.abort_on_exception = true
# Listen for Ctrl-C and disconnect socket gracefully.
Kernel.trap('INT') do
self.disconnect
exit
end
while true
ready = IO.select([#socket])
readable = ready[0]
readable.each do |soc|
if soc == #socket
buf = #socket.recv_nonblock(1024)
if buf.length == 0
puts "The socket connection is dead. Exiting."
exit
else
puts "Received Message"
end
end
end # end each
end # end while
end # end thread
end # end message callback
end
Is there a way I can prevent or catch this error? I'm no expert in socket programming (obviously!), so all help is appreciated.
Your thread is sitting in IO.select() while the trap code happily slams the door in its face with #socket.close, hence you get some complaining.
Don't set abort_on_exception to true, or then handle the exception properly in your code:
Something along these lines...
Kernel.trap('INT') do
#interrupted = true
disconnect
exit
end
...
ready = nil
begin
ready = IO.select(...)
rescue IOError
if #interrupted
puts "Interrupted, we're outta here..."
exit
end
# Else it was a genuine IOError caused by something else, so propagate it up..
raise
end
...

Resources