Multicasting with Ruby?

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

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 )

···

On May 20, 2004, at 03:43, Daemon Le wrote:

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

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

Dan Janowski danj@3skel.com wrote in news:B1BAF9E9-AAE5-11D8-AC4D-
00039357B54C@3skel.com:

···

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 )

Daemon Le wrote:

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

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

···

On May 21, 2004, at 11:55, James Britt wrote:

Daemon Le wrote:

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

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

James Britt jamesUNDERBARb@neurogami.com wrote in
news:40AE24F3.9020006@neurogami.com:

···

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