How to write several tests for a TCPSocket in Minitest - ruby

I try to test a networking application. BUT I might have a knot in my brain.
As far as I know the tests in minitest run parallel. On this assumption I think it is clear that when I allocate a Port in setup() it fails when several tests are run:
RuntimeError: Address already in use - bind(2) for nil port 2000 TCPServer new failed
So what is the best practise to do several tests on a server which listens on a port?
class ServerTest < Minitest::Test
def setup
# -- set env variable
ENV["conf"] = "./tc_gpio.config"
Thread::abort_on_exception = true
#my_thr = Thread.start() do
#server = Server.new
#server.start
assert #server.hi() != nil
end
end
def teardown
Thread.kill(#my_thr) # sends exit() to thr
end
def test_connect_and_disconnect
sleep 1
hostname = 'localhost'
port = 2000
s = TCPSocket.open(hostname, port)
my_s = s.recvmsg()
s.sendmsg(:set.to_s, 0) # Failes since a serialized object is expected
my_s2 = s.recvmsg()
assert_equal( "READY" , my_s[0] )
assert_equal( "eeFIN" , my_s2[0])
end
def test_send_command
# fill command
com = Command.new
com.type = :set
com.device_name = 'pump0'
com.device_address = 'resource0'
com.value = 2
serial_com = YAML::dump(com)
sleep 1
hostname = 'localhost'
port = 2000
s = TCPSocket.open(hostname, port)
my_s = s.recvmsg()
s.sendmsg(serial_com, 0)
my_s2 = s.recvmsg()
assert_equal( "READY" , my_s[0] )
assert_equal( "FIN" , my_s2[0])
end
end

When testing a TCP server in parallel, each instance of the server should be started with a distinct port. That can be done by specifying port number 0 when creating a socket. When port number 0 is given, the socket will be bound to a random unused port:
interface = "0.0.0.0"
port = 0
tcp_server = TCPServer.new(interface, port)
You can find out which port the TCP server socket was bound to:
bound_port = #server_socket.addr[1]
One way to use these facts is to have a server something like this:
class Server
# Create a server instance. If the port is unspecified,
# or 0, then a random ephemeral port is bound to.
def initialize(interface: "127.0.0.1", port: 0)
#server_socket = TCPServer.new(interface, port)
...
end
# Return the port the server socket is bound to.
def bound_port
#server_socket.addr[1]
end
...
end
The test then creates server instances using port 0:
server = Server.new(port: 0)
When making a connection to the server, the test uses the #bound_port accessor to find out what port to connect to:
client = TCPSocket.open("localhost", server.bound_port)
and then carries on normally.

Related

socket conneted using localhost but not 127.0.0.1

i have the following code
require 'socket'
def connect(socket)
while line = socket.gets # Read lines from socket
puts line # and print them
end
socket.close # close socket when done
end
def serve(server)
loop do
client = server.accept # Wait for a client to connect
client.puts "Hello !"
client.puts "Time is #{Time.now}"
client.close
end
end
if ARGV[0] == "-s"
ip_port = ARGV[1]
server = TCPServer.new ip_port
serve(server)
elsif ARGV.length == 2
ip_address = ARGV[0]
ip_port = ARGV[1]
puts ip_address
puts ip_port
socket = TCPSocket.new ip_address , ip_port
connect(socket)
else
puts "PLease enter an IP address and IP port"
end
The code above is a basic server and client. use the -s flag to tell the program to act like a server.
This code works when I use localhost as a address but does not work when I use 127.0.0.1 as the address. I receive a No connection could be made because the target machine actively refused it. - connect(2) error when i use 127.0.0.1. was wondering if anyone knows what the problem is.
I don't think there's anything wrong with the code itself, this looks like more of an issue due to firewall settings or perhaps something else on the machine.
You could always try opening the port and then attempting to telnet into it from a different terminal telnet 127.0.0.1 port. You can also use netstat -atun to check if the port is indeed open and to which address it is bound (0.0.0.0 means accessible by all IP addresses).

Connection won't raise error with bad host

The application I'm working on allows users to add additional connections to other databases through a UI. I'm simply trying to create a validation that ensures that a connection can be made.
I created a separate class to test the DB connections:
class StoredProcConnection < ActiveRecord::Base
def self.abstract_class?
true # So it gets its own connection
end
end
I then create the connection:
def connect
adapter = sql_server? ? 'mssql' : 'mysql2'
default_port = sql_server? ? '1443' : '3306'
#connection_pool = StoredProcConnection.establish_connection(
adapter: adapter,
username: username,
password: password,
host: host,
database: database_name,
port: port || default_port,
timeout: 300)
end
def connection_pool
connect unless #connection_pool
#connection_pool
end
Then I validate it with this method:
def connection_test
if connection_pool.connection
#remove the connection from the StoredProcConnection pool
connection_pool.remove(connection_pool.connection)
return true
else
return false
end
rescue Exception => error
logger.info "unable to create connection with connection.id = #{id} - #{error}"
return false
end
Unfortunately, when it gets to this line with a bad host address like 127.0.0.abcdefg or 666.666.666.666
if connection_pool.connect
The app gets stuck, no errors raised or anything. It just freezes and I have to shut down the server manually.
I have a workaround but it feels quite sloppy. I am just inserting my own timeout in there, but I feel like Active Record should be throwing some kind of error.
def connection_test
Timeout::timeout(3) {
if connection_pool.connection
#remove the connection from the StoredProcConnection pool
connection_pool.remove(connection_pool.connection)
return true
else
return false
end
}
rescue Exception => error
logger.info "unable to create connection with connection.id = #{id} - #{error}"
return false
end
Does anyone see anything that might be causing the freeze? It seems pretty straight forward to me. I'm not sure why the connection pool is even created in the first place with a bad host passed in.
It just freezes...
Odds are good it's not frozen, it's just waiting to see if that connection can be made. TCP/IP has a long timeout value, which fools people into thinking things are frozen, when actually it's being patient.
Few people really understand how the internet works, and how it's really a house built of straw that we try to keep running no matter what. Long IP timeouts are one of the ways that we try to make it self-healing. Software doesn't care about how long something takes, only people care.
Since it appears you're concerned about malformed IP addresses, why aren't you pre-testing them to make sure they're at least in a valid format?
Use Ruby's built-in IPAddr class and try to parse them:
require 'ipaddr'
%w[
127.0.0.abcdefg
666.666.666.666
127.0.0.1
192.168.0.1
255.255.255.255
].each do |ip|
begin
IPAddr.new ip
puts "Good: #{ ip }"
rescue IPAddr::InvalidAddressError => e
puts "#{ e }: #{ ip }"
end
end
# >> invalid address: 127.0.0.abcdefg
# >> invalid address: 666.666.666.666
# >> Good: 127.0.0.1
# >> Good: 192.168.0.1
# >> Good: 255.255.255.255
For "fun reading" there's "TCP Timeout and Retransmission" and "TCP Socket no connection timeout".

Network chat program!! (Need a little help)

I have been working on a small script that allows communication though the TCPsocket command. I am stuck on a small error in my code. For some reason after running it twice the it stops running the RX loop.
I also worry that while its waiting for me to enter something for the get statement, that it won't be looking for incoming messages...
Any help is greatly appreciated. Thanks in advance guys
require 'socket'
ip = 'localhost'
port = 18000
TX = Thread.new do
loop {
Serv = TCPSocket.open(ip, port)
message = gets.chomp()
Serv.write(message)
Serv.close
}
end
RX = Thread.new do
loop {
server = TCPServer.open(port)
client = server.accept
puts client.gets
}
end
RX
TX.join
You should initialize the server outside the loop. (And to avoid warnings, you should not reassign a constant name like Serv in a loop):
require 'socket'
ip = 'localhost'
port = 18000
TX = Thread.new do
loop {
conn = TCPSocket.open(ip, port)
message = gets.chomp()
conn.write(message)
conn.close
}
end
RX = Thread.new do
server = TCPServer.open(port)
loop {
client = server.accept
puts client.gets
}
end
TX.join
If you want to serve multiple clients simultaneously, take a hint from the second example at http://ruby-doc.org/stdlib-1.9.3/libdoc/socket/rdoc/TCPServer.html and use Thread.start(server.accept) { |client| ... }.

Ctrl-c no longer kills my program

I'm writing a ruby back door program that uses PacketFu to listen for packets.
The application works properly, but for some reason it will not exit on interrupt (ctrl+c)
This is the section of code which seems immune to interrupts. It does not matter if I completely empty out the each loop, it will always stall ignore interrupts.
Any ideas?
Edit: I can make the interrupt catch properly when the packet.stream.each loop is executed (Forcing some TCP traffic). It seems that whatever PacketFu does while waiting for new packets is making it immune to interrupts. I guess I will ignore it for now, if anyone knows what PacketFu code might cause this, I'd love to know!
PS. I've Included the entire server code below in case anyone wants test for themselves.
Code Snippet: (See below for entire File)
begin
# Start listening for connection packets via TCP
print "starting up\n"
capturedTCP = PacketFu::Capture.new(:iface => $config[:iface], :start => true, :promisc => true, :filter => "tcp")
print "about to capture\n"
capturedTCP.stream.each { |packet|
puts "Got one!"
pkt = Packet.parse packet
# Check that it is a TCP packet?
if pkt.is_tcp?
# Is it one of our SYN packets?
if pkt.tcp_flags.syn == 1 && pkt.ip_id == $identKey
# TODO: Respond with SYN/ACK
flags = [1,0,0,0,1,0]
payload = ""
tcpResp = tcpConstruct($identKey,srcIP,80,dstIP,Random.rand(65535),flags, payload)
tcpResp.to_w # Sent
# TODO: Use thread instead.
dataListener($identKey,dstIP,dstPort)
end
end
}
rescue Interrupt => e
puts "Interrupted by user"
exit 0
end
Entire File:
require 'rubygems'
require 'packetfu'
require 'thread'
include PacketFu
#TODO: Move these functions to a diff file
# Utility function for loading latest config
def loadConfig(filePath)
#TODO: Load up file and get config
#For now just hard coded.
$iName = "eth0"
identKey = 12345
listenPortMain = "80" # Port to listen for connection requests
processName = "xyz" # Change process name to hide.
filterTCP = "tcp and port #{listenPortMain}"
userCmdField = "src-port" # Options: src-port, dst-port
userCmdRun = "20"
end
# Function for handling client session
def clientListen(ip,port)
# Start listening for connection packets via UDP
capturedUDP = PacketFu::Capture.new(:iface => config[:iface], :start => true, :promisc => true, :filter => "udp and port #{port} and src host #{ip}")
capturedUDP.stream.each { |packet|
pkt = Packet.parse packet
# Check that it is a UDP packet
if pkt.is_udp?
# Is it one of our UDP packets?
if pkt.ip_id == identKey
# Get the data
data = pkt.payload()
# Look for the command type
if userCmdField == "src-port"
cmdFieldVal = pkt.udp_src
end
if userCmdField == "dst-port"
cmdFieldVal = pkt.udp_dst
end
# Command processing
if userCmdField == userCmdRun
cmdDataChrs = [];
# Check for sequence number
seqCurrent = pkt.payload[0].unpack("H*")[0].to_i
seqTotal = pkt.payload[1].unpack("H*")[0].to_i
dataLen = (pkt.payload[2].unpack("H*").chr + pkt.payload[2].unpack("H*").chr).to_i
while pos <= dataLen do
cmdDataChrs.push(pkt.payload[pos])
pos = pos + 1
end
cmdData = cmdDataChrs.unpack("H*")
print "Got command: #{cmdData}"
end
end
end
}
end
#Construct TCP Packet
def tcpConstruct(identKey,srcIP,srcPort,dstIP,dstPort,flags, payload)
#--> Build TCP/IP
#- Build Ethernet header:---------------------------------------
pkt = PacketFu::TCPPacket.new(:config => $config , :flavor => "Linux")
# pkt.eth_src = "00:11:22:33:44:55" # Ether header: Source MAC ; you can use: pkt.eth_header.eth_src
# pkt.eth_dst = "FF:FF:FF:FF:FF:FF" # Ether header: Destination MAC ; you can use: pkt.eth_header.eth_dst
pkt.eth_proto # Ether header: Protocol ; you can use: pkt.eth_header.eth_proto
#- Build IP header:---------------------------------------
pkt.ip_v = 4 # IP header: IPv4 ; you can use: pkt.ip_header.ip_v
pkt.ip_hl = 5 # IP header: IP header length ; you can use: pkt.ip_header.ip_hl
pkt.ip_tos = 0 # IP header: Type of service ; you can use: pkt.ip_header.ip_tos
pkt.ip_len = 20 # IP header: Total Length ; you can use: pkt.ip_header.ip_len
pkt.ip_id = identKey # IP header: Identification ; you can use: pkt.ip_header.ip_id
pkt.ip_frag = 0 # IP header: Don't Fragment ; you can use: pkt.ip_header.ip_frag
pkt.ip_ttl = 115 # IP header: TTL(64) is the default ; you can use: pkt.ip_header.ip_ttl
pkt.ip_proto = 6 # IP header: Protocol = tcp (6) ; you can use: pkt.ip_header.ip_proto
pkt.ip_sum # IP header: Header Checksum ; you can use: pkt.ip_header.ip_sum
pkt.ip_saddr = srcIP # IP header: Source IP. use $config[:ip_saddr] if you want your real IP ; you can use: pkt.ip_header.ip_saddr
pkt.ip_daddr = dstIP # IP header: Destination IP ; you can use: pkt.ip_header.ip_daddr
#- TCP header:---------------------------------------
pkt.payload = payload # TCP header: packet header(body)
pkt.tcp_flags.ack = flags[0] # TCP header: Acknowledgment
pkt.tcp_flags.fin = flags[1] # TCP header: Finish
pkt.tcp_flags.psh = flags[2] # TCP header: Push
pkt.tcp_flags.rst = flags[3] # TCP header: Reset
pkt.tcp_flags.syn = flags[4] # TCP header: Synchronize sequence numbers
pkt.tcp_flags.urg = flags[5] # TCP header: Urgent pointer
pkt.tcp_ecn = 0 # TCP header: ECHO
pkt.tcp_win = 8192 # TCP header: Window
pkt.tcp_hlen = 5 # TCP header: header length
pkt.tcp_src = srcPort # TCP header: Source Port (random is the default )
pkt.tcp_dst = dstPort # TCP header: Destination Port (make it random/range for general scanning)
pkt.recalc # Recalculate/re-build whole pkt (should be at the end)
return pkt
end
def dataListener(identKey,dstIP,dstPort)
# Listen for UDP data packets
print "Listening for Data from #{dstIP}\n"
capturedUDP = PacketFu::Capture.new(:iface => $config[:iface], :start => true, :promisc => true, :filter => "udp and port #{dstPort}")
capturedTCP.stream.each { |packet|
pkt = Packet.parse packet
if pkt.ip_id == identKey
# Get Packet Type
if userCmdField == "src-port"
dataType = pkt.udp_src
elsif userCmdField == "dst-port"
dataType = pkt.udp_dst
end
if dataType == userCmdRun
cmdLen = pkt.payload[0].unpack("c*")
cmd = ""
while i <= cmdLen do
cmd += pkt.payload[i].unpack("h*")
end
print "Command: #{cmd}\n"
end
end
}
end
# - - - - Begin Main
# Get config from file
loadConfig("/path/to/file.txt")
#$config = PacketFu::Config.new(PacketFu::Utils.whoami?(:iface=> iName)).config # set interface
$config = PacketFu::Config.new(:iface=> $iName).config # use this line instead of above if you face `whoami?': uninitialized constant PacketFu::Capture (NameError)
#TODO: Mask process name from config
# Create sessions array (Holds threads)
sessions = []
Signal.trap('INT') { exit 0 }
begin
# Start listening for connection packets via TCP
print "starting up\n"
capturedTCP = PacketFu::Capture.new(:iface => $config[:iface], :start => true, :promisc => true, :filter => "tcp")
print "about to capture\n"
capturedTCP.stream.each { |packet|
puts "Got one!"
pkt = Packet.parse packet
# Check that it is a TCP packet?
if pkt.is_tcp?
# Is it one of our SYN packets?
if pkt.tcp_flags.syn == 1 && pkt.ip_id == $identKey
# TODO: Respond with SYN/ACK
flags = [1,0,0,0,1,0]
payload = ""
tcpResp = tcpConstruct($identKey,srcIP,80,dstIP,Random.rand(65535),flags, payload)
tcpResp.to_w # Sent
# TODO: Use thread instead.
dataListener($identKey,dstIP,dstPort)
end
end
}
end
If your program runs on some flavor of Unix, you could catch the INT signal(sent by Ctrl-C) and call exit explicitly.
Put this before or after your begin block:
Signal.trap('INT') { exit 0 }
Additionally you could do some cleanup before exiting your program:
Signal.trap('INT') do
# some cleanup here
puts "Interrupted by user"
exit 0
end
More on signals in Ruby
Unix signals list

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