|
It’s a bit hard to dig up, but I did figure out how to do UDP packet multicasting in Ruby.
Multicasting
I’ve been playing around with some network peer to peer discovery
techniques and wanted to try some of them out in Ruby. The trick to
self discovery is the ability to send network packets without knowing
the address of the receiver. To do this, you either have to broadcast
or multicast.
Because broadcasted packets are received by every device on the local
network, it is generally considered good manners to not use
broadcasted packets.
Multicasting, on the other hand, is only received by hosts that
explicitly express an interest in the multicast address. Although
abuse of multicasting is still bad manners, it is a better option than
broadcasting.
Sending Multicast Packets
The IP addresses 224.0.0.0 through 239.255.255.255 are reserved for
multicast messages. Simply sending a UDP messsage to an address in
that range is sufficient.
One minor refinement is to set the TTL (Time To Live) option on the
socket. My understanding is that this controls how far the packet is
propagated. As polite net-citizens, we don’t want our multicast
packets travelling too far abroad, so we set the TTL value to 1 (we
need the ugly packing stuff because the value is passed directly to
the C level setsockopt() function with no interpretation by
Ruby).
So the Ruby code to send a multicast message is:
require 'socket'
MULTICAST_ADDR = "225.4.5.6"
PORT= 5000
begin
socket = UDPSocket.open
socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i'))
socket.send(ARGV.join(' '), 0, MULTICAST_ADDR, PORT)
ensure
socket.close
end
Receiving Multicast Messages
Receiving a multicast message is a bit tricker. Since multicast
messages are generally ignored unless someone has explicitly
registered an interest in a particular address, there is a bit of
setup that needs to be done.
Here’s the code for receiving multicast messages:
require 'socket'
require 'ipaddr'
MULTICAST_ADDR = "225.4.5.6"
PORT = 5000
ip = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new("0.0.0.0").hton
sock = UDPSocket.new
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, ip)
sock.bind(Socket::INADDR_ANY, PORT)
loop do
msg, info = sock.recvfrom(1024)
puts "MSG: #{msg} from #{info[2]} (#{info[3]})/#{info[1]} len #{msg.size}"
end
The tricky part was figuring out the right setsockopt options and
values needed to register interest in our multicast address. I had to
do a little reading in the Unix man pages on the C level
setsockopt() function call. The third option to the C
function is a structure that contains two 4-byte IP addresses. The
first IP address is the multicast address, and the second IP address
is the address of the local host adapter that we wish to use to listen
for the multicast. The 0.0.0.0 address means use any of the local
network adapters. IPAddr handles parsing the human readable form of
the IP address and returns a string of 4 bytes in the order needed by
the C level setsockopt() function.
Usage
Save the above code in files named send.rb and
rcv.rb. In one console window, type:
ruby rcv.rb
In another console window on the same or different machine (on the
same local network), type:
ruby send.rb This is a test.
For more fun, bring up several receive windows and all will receive
the messages send by the send script.
I can think of all kinds of fun things to do with this.
Update
I added the Time To Live option on send.
|