Network Programming with Perl


 
Network Programming with Perl

By Lincoln  D.  Stein

Slots : 1

Table of Contents
Chapter  21.   Multicasting

    Content

Using Multicast

The remainder of this chapter shows you how to use multicasting in Perl applications.

Sending Multicast Messages

Sending a multicast message is as straighforward as creating a UDP socket and sending a message to the desired multicast address. Unlike broadcasting messages, to send multicast messages you don't have to get permission from the operating system first.

Recall from Chapter 20 that we were able to identify all the hosts on the local area subnet by pinging the broadcast address. We can use the same trick to identify multicast-capable hosts by sending a packet to the all- hosts group , 224.0.0.1:

% ping 224.0.0.1 PING 224.0.0.1 (224.0.0.1): 56 data bytes 64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=1.0 ms 64 bytes from 143.48.31.47: icmp_seq=0 ttl=255 time=1.2 ms (DUP!) 64 bytes from 143.48.31.46: icmp_seq=0 ttl=255 time=1.3 ms (DUP!) 64 bytes from 143.48.31.55: icmp_seq=0 ttl=255 time=1.5 ms (DUP!) 64 bytes from 143.48.31.43: icmp_seq=0 ttl=255 time=1.7 ms (DUP!) 64 bytes from 143.48.31.61: icmp_seq=0 ttl=255 time=1.9 ms (DUP!) 64 bytes from 143.48.31.33: icmp_seq=0 ttl=255 time=2.1 ms (DUP!) 64 bytes from 143.48.31.36: icmp_seq=0 ttl=255 time=2.2 ms (DUP!) 64 bytes from 143.48.31.45: icmp_seq=0 ttl=255 time=2.8 ms (DUP!) 64 bytes from 143.48.31.32: icmp_seq=0 ttl=255 time=3.0 ms (DUP!) 64 bytes from 143.48.31.48: icmp_seq=0 ttl=255 time=3.1 ms (DUP!) 64 bytes from 143.48.31.58: icmp_seq=0 ttl=255 time=3.3 ms (DUP!) 64 bytes from 143.48.31.57: icmp_seq=0 ttl=255 time=3.5 ms (DUP!) 64 bytes from 143.48.31.40: icmp_seq=0 ttl=255 time=3.7 ms (DUP!) 64 bytes from 143.48.31.39: icmp_seq=0 ttl=255 time=3.9 ms (DUP!) 64 bytes from 143.48.31.31: icmp_seq=0 ttl=255 time=4.0 ms (DUP!) 64 bytes from 143.48.31.34: icmp_seq=0 ttl=255 time=4.5 ms (DUP!) 64 bytes from 143.48.31.37: icmp_seq=0 ttl=255 time=4.7 ms (DUP!) 64 bytes from 143.48.31.38: icmp_seq=0 ttl=255 time=4.9 ms (DUP!) 64 bytes from 143.48.31.41: icmp_seq=0 ttl=64 time=5.1 ms (DUP!) 64 bytes from 143.48.31.35: icmp_seq=0 ttl=255 time=5.2 ms (DUP!)

As in the earlier broadcast example, a variety of machines responded to the ping, including the loopback device (127.0.0.1) and a mixture of UNIX and Windows machines. Unlike the broadcast example, two laser printers on the subnetwork did not respond to the multicast call, presumably because they are not multicast capable. Similarly, we could ping 224.0.0.2, the all-routers group, to discover all multicast-capable routers on the LAN, 224.0.0.4 to discover all DVMRP routers, and so forth.

For a Perl script to send a multicast message, it has only to create a UDP socket and send to the desired group address. To illustrate this, we can use the broadcast echo client from the previous chapter (Figure 20.2) to discover all multicast-capable hosts on the local subnet that are running an echo server. The program doesn't need modification; instead of giving the broadcast address as the command-line argument, we just use the address for the all-hosts group:

broadcast_echo_cli.pl 224.0.0.1 hi there received 9 bytes from 143.48.31.42:7 received 9 bytes from 143.48.31.30:7 received 9 bytes from 143.48.31.40:7

Interestingly, the list of servers that respond to the echo client is much smaller than it was for either the multicast ping test or the broadcast ping test of the previous chapter. After some investigation, the difference turned out to be nine Solaris machines whose kernels were not configured for multicasting. Apparently there was sufficient low-level multicasting code built into the kernel of these machines to allow them to respond to ICMP ping messages, but not to higher-level multicasts.

Socket Options for Multicast Send

By default, when you issue a multicast message, it is sent from the default interface with a TTL of 1. In addition, the message " loops back" to the same host you sent it in the same manner that broadcast packets do. You can change one or more of these defaults using a set of three IP-level socket options that are specific for multicasting.

IP_MULTICAST_TTL The IP_MULTICAST_TTL option gets or sets the TTL for outgoing packets on this socket. Its argument must be an integer between 1 and 255, packed into binary using the "I" format.

IP_MULTICAST_LOOP IP_MULTICAST_LOOP activates or deactivates the loopback property of multicast messages. Its argument, if true, causes outgoing multicast messages to loop back so that they are received by the host (the default behavior). If false, this behavior is suppressed. Note that this has nothing to do with the loopback interface.

IP_MULTICAST_IF The IP_MULTICAST_IF socket option allows you to control which network interface the multicast message will be issued from, much as you can control where broadcast packets go by choosing the appropriate broadcast address. The argument is the packed IP address of the interface created using inet_aton() . If an interface is not explicitly set, the operating system picks an appropriate one for you.

There is a "gotcha" when using getsockopt() to retrieve the value of IP_MULTICAST_IF under the Linux operating system. This OS accepts the packed 4-byte interface address as the argument to setsockopt() but returns a 12-byte ip_mreqn structure from calls to getsockopt() (see the following description of the related ip_mreq structure). This undocumented behavior is a bug that will be fixed in kernel versions 2.4 and higher. We work around this behavior in the IO::Multicast module developed in the next section.

Unlike all the other socket options we have seen so far, the multicast options apply not to the socket at the SOL_SOCKET level but to the IP protocol layer (which is responsible for routing packets and other functions related to IP addresses). The second argument passed to getsockopt() and setsockopt() must be the protocol number for the IP layer, which you can retrieve by calling getprotobyname () using 'IP' as the protocol name . This example illustrates how to turn off loopback for a socket named $soc :

my $ip_level = getprotobyname('IP') or die "Can't get protocol: $!"; setsockopt($sock,$ip_level,IP_MULTICAST_LOOPBACK,0);

Because the IO::Socket->sockopt() method assumes the SOL_SOCKET level, you cannot use it for multicast options. However, you can use IO::Socket's setsockopt() and getsockopt() methods , which are just thin wrappers around the underlying Perl function calls.

The multicast option constants are defined in the system header file netinet/in.h . To get access to the proper values for your operating system, you must use the h2ph tool to convert the system header files.

Receiving Multicast Messages

Multicast messages are sent to the combination of a multicast group address and a port. To receive them, your program must create a UDP socket, bind it to the appropriate port, and then join the socket to one or more multicast addresses.

A single socket can belong to multiple multicast groups simultaneously , in which case the socket receives all messages sent to any of the groups that it currently belongs to. The socket also continues to receive messages directed to its unicast address. The number of groups that a socket can belong to is limited by the operating system; a limit of 20 is typical.

Two new socket options allow you to join or leave multicast groups: IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP .

IP_ADD_MEMBERSHIP Join a multicast group, thereby receiving all group transmissions directed to the port the socket is bound to. The argument is a packed binary string consisting of the desired multicast address concatenated to the address of the local interface (derived from a C structure called an ip_mreq ). This allows you to control not only what multicast groups to join but also on which interface to receive their messages. If you are willing to accept multicast transmissions on any interface, use INADDR_ANY as the local interface address.

The outgoing multicast interface (set by IP_MULTICAST_IF ) is not tied in any way to the interface used to receive multicast packets. You can send multicast packets from one network interface and receive them on another.

IP_DROP_MEMBERSHIP Leave a multicast group, terminating membership in the group. The argument is identical to the one used by IP_ADD_MEMBERSHIP .

As with IP_MULTICAST_IF and the other options discussed earlier, the IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP options apply to the IP layer, so you must pass setsockopt() an option level equal to the IP protocol number returned by getprotobyname() .

These two options may sound more complicated than they are. The only tricky part is creating the ip_mreq argument to pass to setsockopt() . You can do by passing the group address to inet_aton() and then concatenating the result with the INADDR_ANY constant. This code snippet shows how to join a multicast group, in this case the one with address 225.1.1.3:

my $mcast_addr = inet_aton('225.1.1.3'); my $local_addr = INADDR_ANY; my $ip_mreq = $mcast_addr . $local_addr; my $ip_level = getprotobyname('IP') or die "Can't get protocol: $!"; setsockopt($sock,$ip_level,IP_ADD_MEMBERSHIP,$ip_mreq) or die "Can't join group: $!";

You drop membership in a group in the same way, using the IP_DROP_MEMBERSHIP constant. You do not have to drop membership in all groups before exiting the program. The operating system will take care of this for you when the socket is destroyed .

Oddly, there is no way to ask the operating system what multicast groups a socket is a member of. You have to keep track of this yourself.

The IO::Socket::Multicast Module

This section develops a small module that makes getting and setting multicasting options more convenient . As in the IO::Interface module discussed in the previous chapter, it is a pure-Perl solution that gets system constants from h2ph-generated.ph files. You'll find a C-language version of IO::Multicast on CPAN, and if you have a C or C++ compiler handy, I recommend that you install it rather than hassling with h2ph .

IO::Socket::Multicast is a descendent of IO::Socket::INET. It implements all its parents' methods and adds several new methods related to multicasting. As a convenience, this module makes UDP, rather than TCP, the default protocol for new socket objects.

$socket->mcast_ttl([$ttl])

Get or set the socket's multicast time to live. If you provide an integer argument, it will be used to set the TTL and the method returns true if the attempt was successful. Without an argument, mcast_ttl() returns the current value of the TTL.

$socket->mcast_loopback([$boolean])

Get or set the loopback property on outgoing multicast packets. Provide a true value to enable loopbacking, false to inhibit it. The method returns true if it was successful. Without an argument, the method returns the current loopback setting.

$socket->mcast_if([$if])

Get or set the interface for outgoing multicast packets. For your convenience, you can use either the interface device name, such as eth0 , or its dotted -quad interface address. The method returns true if the attempt to set the interface was successful. Without any argument, it returns the current interface, or if no interface is set, it returns undef (in which case the operating system chooses an appropriate interface automatically).

$socket->mcast_add($multicast_group [,$interface])

Join a multicast group, allowing the socket to receive messages multicast to that group.

Specify the group address using dotted-quad form (e.g., "225.0.0.3"). The optional second argument allows you to tell the operating system which network interface to use to receive the group. If not specified, the OS listens on all multicast-capable interfaces. For your convenience, you can use either the interface device name or the interface address.

This method returns true if the group was successfully added; otherwise , it returns false. In case of failure, $! contains additional information.

You may call mcast_add() more than once in order to join multiple groups.

$socket->mcast_drop($multicast_group [,$interface])

Drop membership in a multicast group, disabling the socket's reception of messages to that group. Specify the group using its dotted-quad address. If you specified the interface in mcast_add(), you must again specify that interface when leaving the group. [1] You may use either a device name or an IP address to specify the interface.

This method returns true if the group was added successfully, and false in case of an error, such as dropping a group to which the socket does not already belong.

[1] This behavior varies somewhat among operating systems. With some, if you omit the interface, the operating system drops the first matching multicast group it finds. With others, the interface argument to mcast_add() must exactly match mcast_drop() .

Figure 21.3 contains the complete code for the IO::Socket:: Multicast module. We'll walk through the relevant bits.

Figure 21.3. The IO::Socket::Multicast module

Lines 1 “7: Module setup The first part of the module consists of boilerplate module declarations and bookkeeping. Among other things, we bring in the IO::Interface module developed in the previous chapter and declare this module a subclass of IO::Socket::INET.

Lines 8 “12: Bring in Socket and .ph definitions We next load functions from the Socket module and from netinet/in.ph . As in the IO::Interface module, to avoid clashing prototype warnings from duplicate functions defined in the .ph file, we call the Socket module's import() method manually. netinet/in.ph contains definitions for the various IFF_MULTICAST socket options.

We call getprotobyname() to retrieve the IP protocol number for use with setsockopt() and getsockopt() . If the protocol number isn't available for some reason, we default to 0, which is a common value for this constant.

Lines 13 “22: The new() and configure() methods We override the IO::Socket::INET new() and configure() methods so as to make UDP the default protocol if no Proto argument is given explicitly.

Lines 23 “29: mcast_add() The mcast_add() method receives the socket, the multicast group address, and the optional local interface to receive on. If an interface is specified, the method calls the internal function get_if_addr() to deal appropriately with the alternative ways that the interface can be specified. If no interface is specified, then get_if_addr() returns "0.0.0.0", the dotted-quad form of the INADDR_ANY wildcard address.

We then build an ip_mreq structure by concatenating the binary forms of the group and local IP address, and pass this to setsockopt() with a socket level of $IP_LEVEL and a command of IP_ADD_MEMBERSHIP .

Lines 30 “36: mcast_drop() This method contains the same code as mcast_add() , except that at the very end it calls setsockopt() with a command of IP_DROP_MEMBERSHIP .

Lines 37 “47: mcast_if() This method assigns or retrieves the interface for outgoing multicast messages. If the caller has specified an interface, we turn it into an address by calling get_if_addr() , translate it into its packed binary version using inet_aton() , and call setsockopt() with the IP_MULTICAST_IF command.

For retrieving the interface, things are slightly more complicated because of buggy behavior under the Linux operating system, where getsockopt() returns a 12-byte ip_mreqn structure rather than the expected 4-byte packed IP address of the interface (I found this out by examining the kernel source code). The desired information resides in the second field of this structure, beginning at byte number 4. We test the length of the getsockopt() result, and if it is larger than 4, we extract the address using substr() . We then call an internal routine named find_interface() to turn this IP address into an interface device name.

Lines 48 “56: The mcast_loopback () method The mcast_loopback() method is more straightforward. If a second argument is supplied, it calls setsockopt() with a command of IP_MULTICAST_LOOP and an argument of 1 to turn loopback on and 0 to turn loopback off. If no argument is supplied, then the method calls getsockopt() to retrieve the loopback setting. getsockopt() returns the setting as a packed binary string, so we convert it into a human-readable number by unpacking it using the " I " (unsigned integer) format.

Lines 57 “65: mcast_ttl() The mcast_ttl() method gets or sets the TTL on outgoing multicast messages. If a TTL value is specified, we pack it into a binary integer with the " I " format and pass it to setsockopt() with the IP_MULTICAST_TTL command. If no value is passed, we reverse the process.

Lines 66 “75: get_if_addr() function The last two functions are used internally. get_if_addr() allows the caller to specify network interfaces using either a dotted IP address or the device name. The function takes two arguments consisting of the socket and the interface. If the interface argument is empty, then the function returns "0.0.0.0," which is the dotted-quad equivalent of the INADDR_ANY wildcard. If the interface looks like a dotted-quad address by pattern match, then the function returns it unmodified.

Otherwise, we assume that the argument is a device name. We call the socket's if_addr() method (created by IO::Interface) to retrieve the corresponding interface address. If this is unsuccessful , we die with an error message. As a consistency check, we call the if_flags() method to confirm that the interface is multicast-capable; if it is not, we die. Otherwise, we return the interface address.

Lines 76 “82: find_interface() function The last function performs the reverse of get_if_addr() , returning the interface device name corresponding to an IP address. It retrieves the list of device names by calling the socket's if_list() method (defined in IO::Interface) and loops over them until it finds the one with the desired IP address.


   
Top

Категории