Can I receive data using multiple UDP Ports in Ruby? - ruby

Is there a way I can listen to more than one port on UDPServer in Ruby?
I need to listen to some devices that send me data over multiple ports, and I need to receive them and use the same operations on the data.
I'm right now using:
udp_socket = UDPSocket.new
udp_socket.bind( "", 8500 )
while($running) do
payload, host = udp_socket.recvfrom(1500)
process payload
end
but the code stops on:
payload, host = udp_socket.recvfrom(1500)
and I cannot run more code until it receives data.

You could launch multiple threads.
require 'thread'
ports = [1500, 1501, 1502]
# Create threads to listen to each port
threads = ports.map do |port|
Thread.new do
while($running) do
payload, host = udp_socket.recvfrom(port)
process payload
end
end
end
# Wait for all threads to complete
threads.map &:join

Related

TCP packets recieved on interface but not by Ruby TCPServer

I have a Ruby TCPServer that does not recieve TCP packets intermittently. Note the below is run inside another threads << Thread.new do as I have multiple TCP post listeners.
server = TCPServer.new(7207)
Thread.start(server.accept) do |client|
# process packet, send to AWS SQS
raw = ""
while (line = client.gets)
raw += line
end
sender = client.peeraddr
text = raw.unpack1("H*")
message_body = { payload: text, rx_at: Time.current, sender:}
puts "#{Time.current} : --- New uplink #{message_body}"
# send message to AWS SQS
client.close
end
I see the packets in tcpdump / wireshark but not on my TCPServer. I have a pcap file available:
https://www.dropbox.com/s/7m3hr1b7065tenx/tcp.pcap?dl=0
Example lost packets occurred on:
ip 10.0.225.43 at 27/07/2022 20:56:57 and 27/07/2022 20:39:31
Apologies after checking Wireshark this looks like a network problem not a Ruby / dev problem. The device was not sending FIN frames so the network stack was not publishing the packet up to my app.

Send data to a decent user with Kemal over websocket

How can i send data data to a decent user connected via websockets? I know,
Websocket connections yields the context, but how can i filter a decent socket connection for sending data to only 1 (or some) connected user(s) depending on context (env)?
SOCKETS = [] of HTTP::WebSocket
ws "/chat" do |socket,env|
room = env.params.query["room"]
SOCKETS << socket
socket.on_message do |message|
SOCKETS.each { |socket| socket.send message}
end
socket.on_close do
SOCKETS.delete socket
end
end
Must socket contain the room or needs SOCKETS to be a Hash?
You can store sockets as hash and give id to your clients and then you can send saved socket of your recent client.
I mean that
SOCKETS = {} of String => HTTP::WebSocket
socket.on_message do |message|
j = JSON.parse(message)
case j["type"]
when "login"
user_id = j["id"].to_s
SOCKETS[user_id] = socket
when "whisper"
to = j["to"].to_s
from = j["from"]
user = SOCKETS[to].send("#{from} is whispering you!")
end
something like that.

Parse TCP Packet and return response PHP or Ruby

I am trying to gather information from a TCP connection made to my web server to assist in our troubleshooting efforts, similar to http://speedguide.net/analyzer.php tool.
We have a simple PHP server script test page that users connect that returns their private IP to an AJAX call waiting for the response.
I would like to either build on that or prefer using Ruby. I played with the PacketFu lib and get all the information I think I need, however, I'm having trouble with the recipe to combine it all:
listen on port x,
parse the packet
respond back to client.
Using Ruby's TCPServer I can easily handle 1 and 3. With Packetfu, 2.
I've coded with PHP in the past but only HTML-based, no sockets. And I'm not really all that familiar with Ruby sockets either.
Though the packet stream and client.accept don't seem to play nice. The packets aren't always IP or TCP meeting the Packetfu::Packet.is_ip? or is_tcp?.
Could someone point me in the right direction or give me some practical example of how I might combine the two, or adjust my thinking on how I would accomplish this task?
This is the Playground code:
require 'socket'
require 'json'
require 'packetfu'
iface = ARGV[0] || "eno1"
server = TCPServer.open(31337)
cap = PacketFu::Capture.new(:iface => iface, :start => true, :promisc => true)
loop {
cap.stream.each do |p|
pkt = PacketFu::Packet.parse(p)
if pkt.is_ip? || pkt.is_tcp?
if pkt.tcp_dport == 31337
print "Source Addr: #{pkt.ip_saddr}\n"
print "Source Port: #{pkt.tcp_src}\n"
print "Destination Addr: #{pkt.ip_daddr}\n"
print "Destination Port: #{pkt.tcp_dport}\n"
print "TCP Options: #{pkt.tcp_options.inspect}\n"
print "TCP Win: #{pkt.tcp_win}\n"
print "TCP SYN?: #{pkt.tcp_flags.syn}\n"
print "TCP ACK?: #{pkt.tcp_flags.ack}\n"
print "TCP FLAGS ALL: #{pkt.tcp_flags.inspect}\n"
print "TTL: #{pkt.ip_ttl}\n"
print "IP FRAG: #{pkt.ip_frag}\n"
end
end
client = server.accept # Wait for a client to connect
h = { ipaddress: client.peeraddr[2] }
client.puts h.to_json
client.close
end
}
This is the output:
Source Addr: 172.20.0.15
Source Port: 41165
Destination Addr: 172.20.0.10
Destination Port: 31337
TCP Options: "NOP,NOP,TS:216432150;57946250"
TCP Win: 229
TCP SYN?: 0
TCP ACK?: 1
TCP FLAGS ALL: #<struct PacketFu::TcpFlags urg=0, ack=1, psh=0, rst=0, syn=0, fin=0>
TTL: 61
IP FRAG: 16384
This is the browser response:
{"ipaddress":"172.20.0.15"}

Ruby, get incoming address from UDP message

I have a UDP server that binds to all addresses on a system, I would like to know what ip address the message was addressed to. Any ideas how to do this?
Here is my example code:
sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
sock.bind(Addrinfo.udp('', 2400))
while(true)
sockset = IO.select([sock])
sockset[0].each do |sock|
data = sock.recvfrom(1024)
puts "data: " + data.inspect
end
end
sock.close
This will produce something like:
data: ["test message\n", #<Addrinfo: 172.16.5.110:41949 UDP>]
Am I able to set a socket option, or something, to return the local IP?
Just a note, this needs to work for IPv6 too. Thanks in advance, Dave.
UNIX Network Programming has this to say about this very subject:
With a UDP socket, however, the destination IP address can only be obtained by setting the IP_RECVDSTADDR socket option for IPv4 or the IPV6_PKTINFO socket option for IPv6 and then calling recvmsg instead of recvfrom.
Ruby’s socket library has recvmsg which is a bit easier to use than the underlying C function, but still needs a bit of work to get the info needed. The destination IP address is included in the array of ancillary data returned from recvmsg. Here’s a version of your code adapted to use recvmsg and get the destination address for IPv4:
require 'socket'
require 'ipaddr'
sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
# Set the required socket option:
sock.setsockopt :IPPROTO_IP, :IP_RECVDSTADDR, true
sock.bind(Addrinfo.udp('0.0.0.0', 2400))
while(true)
sockset = IO.select([sock])
sockset[0].each do |sock|
mesg, sender, _, *anc_data = sock.recvmsg
# Find the relevant data and extract it into a string
dest = IPAddr.ntop(anc_data.find {|d| d.cmsg_is?(:IP, :RECVDSTADDR)}.data)
puts "Data: #{mesg}, Sender: #{sender.ip_address}, Destination: #{dest}"
end
end
And here is a version for IPv6. There is also a RECVPKTINFO socket option, which I think may have superseded PKTINFO – depending on your system you may need to use that instead.
require 'socket'
sock = Socket.new(Socket::AF_INET6, Socket::SOCK_DGRAM, 0)
# Set the socket option for IP6:
sock.setsockopt :IPPROTO_IPV6, :IPV6_PKTINFO, true
sock.bind(Addrinfo.udp('0::0', 2400))
while(true)
sockset = IO.select([sock])
sockset[0].each do |sock|
mesg, sender, _, *anc_data = sock.recvmsg
# Find and extract the destination address
dest = anc_data.find {|d| d.cmsg_is?(:IPV6, :PKTINFO)}.ipv6_pktinfo_addr
puts "Data: #{mesg}, Sender: #{sender.ip_address}, Destination: #{dest.ip_address}"
end
end
Ruby also provides a Socket.udp_server_loop method, which yields the message and a UDPSource object to the block you provide, and this source object has a local_address field. Looking at the source this appears to check the PKTINFO data like I do above to get the destination address for IPv6 requests, but not for IPv4. This method binds to all available IP addresses individually, and just uses the address of the incoming interface for IP4 requests, which may not be accurate for a weak end system model. However it might be simpler for you to use Socket.udp_server_loop.

Ruby + AMQP: processing queue in parallel

Since most of my tasks depends on the network, I want to process my queue in parallel, not just one message at a time.
So, I'm using the following code:
#!/usr/bin/env ruby
# encoding: utf-8
require "rubygems"
require 'amqp'
EventMachine.run do
connection = AMQP.connect(:host => '127.0.0.1')
channel = AMQP::Channel.new(connection)
channel.prefetch 5
queue = channel.queue("pending_checks", :durable => true)
exchange = channel.direct('', :durable => true)
queue.subscribe(:ack => true) do |metadata, payload|
time = rand(3..9)
puts 'waiting ' + time.to_s + ' for message ' + payload
sleep(time)
puts 'done with '+ payoad
metadata.ack
end
end
Why it is not using my prefetch setting? I guess it should get 5 messages and process them in parallel, no?
Prefetch is the maximum number of messages that may be sent to you in advance before you ack.
In other words, the prefetch size does not limit the transfer of single messages to a client, only the sending in advance of more messages while the client still has one or more unacknowledged messages. (From AMPQ docs)
QoS Prefetching Messages
RabbitMQ AMQP Reference
EventMachine is single threaded and event based. For parallel jobs on different threads or processes, see EM::Deferrable, then Thread or spawn.
Also see Hot Bunnies, a fast DSL on top of the RabbitMQ Java client:
https://github.com/ruby-amqp/hot_bunnies
(Thanks for info from Michael Klishin on Google Groups, and stoyan on blogger)

Resources