Ruby - Send message to multiple IP addresses - ruby

How would I go about sending one message to multiple receivers?
This is essentially what I would like to do:
require 'socket'
ip = ['IP 1', 'IP 2']
port = 18000
loop {
message = gets.chomp()
conn = TCPSocket.open(ip, port)
conn.write(message)
conn.close_write
}

Try to iterate over the ips array:
ips = ['IP 1', 'IP 2']
port = 18000
loop do
message = gets.chomp()
ips.each do |ip|
conn = TCPSocket.open(ip, port)
conn.write(message)
conn.close_write
end
end

Related

How to write several tests for a TCPSocket in Minitest

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.

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

How to establish a SSL enabled TCP/IP Connection in Ruby

I need to establish a TCP connection with my server which has a SSL enabled port, that I need to access.
I need to send a XML file and get the response from the server.
Before the SSL was enabled, I was able to get the data from the server using the below mentioned code.
require 'socket'
myXML = 'test_xml'
host = 'myhost.com'
port = 12482
socket = TCPSocket.open(host,port) # Connect to server
socket.send(myXML, 0)
response = socket.recvfrom(port)
puts response
socket.close
Now I have a 'certi.pfx' with which I need to establish a connection, Send my_xml data and get the response. How can this be done.
I would also like to know if I have the 'pem' and 'key' file, how can I establish a connection, Send my_xml data and get the response.
Please help.
require 'socket'
require 'openssl'
myXML = 'my_sample_data'
host = 'my_host.com'
port = my_port
socket = TCPSocket.open(host,port)
ssl_context = OpenSSL::SSL::SSLContext.new()
ssl_context.cert = OpenSSL::X509::Certificate.new(File.open("certificate.crt"))
ssl_context.key = OpenSSL::PKey::RSA.new(File.open("certificate.key"))
ssl_context.ssl_version = :SSLv23
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
ssl_socket.sync_close = true
ssl_socket.connect
ssl_socket.puts(myXML)
while line = ssl_socket.gets
p line
end
ssl_socket.close
Like this:
sock = TCPSocket.new('hostname', 443)
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
#socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
socket.sync_close = true
socket.connect
end

Creating a sequence of IP addresses if given a starting address

I need to write code to ping a sequence of 20 IP addresses if given a starting IP address (e.g 192.168.0.1). Each successive IP address should be one digit larger than the previous.
That's what IPAddr#succ is for:
require 'ipaddr'
ipaddr = IPAddr.new('192.168.0.1')
20.times do
ping ipaddr
ipaddr = ipaddr.succ
end
ip = "192.168.0.1"
ips = []
(0..20).each do |n|
temp = ip.split('.').map(&:to_i)
temp[3] = temp[3] + n
ips << temp.join('.')
end
puts ips

Resources