Multicasting with Ruby?

D

Daemon Le

Hi,


I'm trying to "listen" on a multicast using Ruby. After trolling the
news groups and "google", I was able to find a rudimentary set of code in
my attempt to listen on multicasting, but it doesn't quite work and I
don't know why? Does anyone know of good resources (webpage or book) for
multicasting with Ruby? Any help would be appreciated.

Here's the sample code that "semi" works...

--- begin ------------------------------------
require 'socket'

port = 1212
addr = '228.5.6.8'
host = Socket.gethostname
maddr = addr.split('.').collect! { |b| b.to_i }.pack('CCCC')
mreq = maddr + Socket.gethostbyname(host)[3]
sock = UDPSocket.new

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)
sock.bind(host, port)
sock.connect(host, port)

# Check send ...
sock.send('Hello', 0, addr, port)
sock.send('World', 0, addr, port)

# Check listen ...
count=0
5.times {
count += 1
p "COUNT = #{count}"
p sock.recvfrom(8)
}

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_DROP_MEMBERSHIP, mreq)

exit( 0 )
--- end --------------------------------------

The "Check send" works just fine to send into the multicast address. I
check this while listening with tcpdump (i.e., tcpdump 'ip multicast and
src port 1212 and host 228.5.6.8'). But I'm unable to pick up on the
"Check listen" part. It just basically hangs with "COUNT = 1" and waits
forever even though I can clearly see packets being transmitted over the
multicast address in question.

FYI. I'm starting to look at the "rb_spread" module, but I would rather
try to do this without first...


- Daemon
 
D

Dan Janowski

I got your program running under solaris. Solaris has some alignment
padding in the sockaddr struct, so the [4..7] is needed. I added
address reuse, though on *BSD it should be SO_REUSEPORT which is not a
ruby constant; for Solaris SO_REUSEADDR works.

The only thing I materially changed was to remove the connect and put
the add_membership after the bind.

Hope this works for you.

Dan


require 'socket'

port = 1212
addr = '228.5.6.8'
host = Socket.gethostname
maddr = addr.split('.').collect! { |b| b.to_i }.pack('CCCC')

# mreq from netinet/in.h
# in_addr multicast address
# in_addr local IP address of interface

sa=(Socket.gethostbyname(host)[3])[4..7]

mreq = maddr + sa

sock = UDPSocket.new
sock.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR,1)
sock.bind(addr, port)

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)

# Check send ...
sock.send('Hello', 0,addr,port)
sock.send('World', 0,addr,port)

# Check listen ...
count=0
5.times {
count += 1
p "COUNT = #{count}"
p sock.recvfrom(8)
}

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_DROP_MEMBERSHIP, mreq)

exit( 0 )
 
D

Daemon Le

Thanks Dan! It worked beautifully. Changing "host" to "addr" (OK, that
was dumb on my part), getting rid of the connect, and doing
IP_ADD_MEMBERSHIP after bind fixed all my current woes. Here's with your
corrections as it runs on the Redhat Linux box...

--- begin ----------------------------------------
require 'socket'

port = 1212
addr = '228.5.6.8'
host = Socket.gethostname
maddr = addr.split('.').collect! { |b| b.to_i }.pack('CCCC')
mreq = maddr + Socket.gethostbyname(host)[3]

sock = UDPSocket.new
sock.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, 1)
sock.bind(addr, port)

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)

# Check send ...
sock.send('Hello', 0, addr, port)
sock.send('World', 0, addr, port)

# Check listen ...
count=0
5.times {
count += 1
p "COUNT = #{count}"
p sock.recvfrom(8)
}

sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_DROP_MEMBERSHIP, mreq)

exit( 0 )
--- end ------------------------------------------


Thanks again,
Daemon
 
J

James Britt

Daemon said:
Thanks Dan! It worked beautifully. Changing "host" to "addr" (OK, that
was dumb on my part), getting rid of the connect, and doing
IP_ADD_MEMBERSHIP after bind fixed all my current woes. Here's with your
corrections as it runs on the Redhat Linux box...

--- begin ----------------------------------------
require 'socket'

port = 1212
addr = '228.5.6.8'

Pardon my networking ignorance, but in this example, where does the
address 228.5.6.8 come from? Or does it matter much?

I'm trying learn more about multicasting, but when I run the example on
my Red Hat box I get

mcast.rb:13:in `setsockopt': No such device (Errno::ENODEV)

where line 13 is
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)

ruby -v gives:
ruby 1.8.1 (2003-12-25) [i586-linux]


Thanks,


James
 
D

Dan Janowski

This particular address is:

$ host 228.5.6.8
8.6.5.228.in-addr.arpa domain name pointer
reserved-multicast-range-NOT-delegated.example.com.

An official mcast test address. Find out more about multicast
addressing here:

http://rfc3171.x42.com/

I'm not sure why Your instance of Red Hat would fail to run. Do you
have to compile multicast into the Kernel?

Dan

Daemon said:
Thanks Dan! It worked beautifully. Changing "host" to "addr" (OK,
that was dumb on my part), getting rid of the connect, and doing
IP_ADD_MEMBERSHIP after bind fixed all my current woes. Here's with
your corrections as it runs on the Redhat Linux box...
--- begin ----------------------------------------
require 'socket'
port = 1212
addr = '228.5.6.8'

Pardon my networking ignorance, but in this example, where does the
address 228.5.6.8 come from? Or does it matter much?

I'm trying learn more about multicasting, but when I run the example
on my Red Hat box I get

mcast.rb:13:in `setsockopt': No such device (Errno::ENODEV)

where line 13 is
sock.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, mreq)

ruby -v gives:
ruby 1.8.1 (2003-12-25) [i586-linux]


Thanks,


James
 
D

Daemon Le

As Dan had stated, I chose the multicast address arbitrarily. Up to you
which one you decide to use. But do check the RFC.

I'm not sure why you are getting the error either. But just for your own
info, I'm using Ruby v1.6.8 (manually compiled but default options) on
Redhat 7.3. I believe they're standard, but I've also verified IPPROTO_IP
and IP_ADD_MEMBERSHIP numbers are defined in the include files (on my box,
/usr/include/linux/in.h, /usr/include/netinet/in.h for IPPROTO..., and
usr/include/linux/in.h, /usr/include/bits/in.h for IP_ADD...).

It is also true that the kernel must be compiled with
"CONFIG_IP_MULTICAST=y". But I believe this is also pretty normal for
Redhat distributed kernels.

Good luck to you. I'm learning myself.


- Daemon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top