Enhancing the Chat Client to Support Resource Discovery


 
Network Programming with Perl
By Lincoln  D.  Stein
Slots : 1
Table of Contents
Chapter  20.   Broadcasting

    Content

Broadcasting Without the Broadcast Address

Before you can broadcast, you must know the appropriate broadcast address for your subnet. In the previous examples, we hard-coded that address; but for portable, general-purpose utilities, it would be nice not to have to do this.

The are two ways to broadcast if you don't know the correct broadcast address in advance. One is to use the all-ones broadcast address as the target. The other is to determine the host's broadcast address at runtime.

The All-Ones Broadcast Address

The easiest way to broadcast when you don't know the subnet-directed broadcast address in advance is to use the all-ones broadcast address, 255.255.255.255. This address was developed primarily for use by diskless workstations, which don't know their IP address when they boot up and must get that and other configuration information from a boot server located somewhere on the network. In order to bootstrap this process, diskless workstations broadcast a plea for help, using the all-ones address as their target. If a boot server responds, the two can begin the configuration process.

The all-ones broadcast address acts very much like the subnet-directed broadcast address: It is distributed to all hosts on the subnet and is not forwarded by routers. In most cases you can simply substitute the all-ones broadcast address for the subnet-directed address and your applications will work as before. To demonstrate this, give the previous section's echo client a destination address of 255.255.255.255. It should work as it did with the subnet-directed address.

Before you use the all-ones address, however, you should know that it has a number of minor "gotchas." On hosts with multiple network interfaces, broadcasting to the all-ones address has different consequences on different operating systems. Some operating systems direct the broadcast to each network interface in turn so that it appears on all subnets to which the host is attached. On other systems, the broadcast is issued just once on the host's default outgoing interface. In others, the operating system silently transforms all-ones broadcasts into subnet-directed broadcasts. These slight differences in behavior are invisible most of the time, but they may cause problems with picky servers and can be confusing if you're trying to diagnose things with a packet sniffer.

Older versions of the Linux and the QNX operating systems are also known to have trouble sending to the all-ones address and need to have a host with address 255.255.255.255 manually inserted into the routing table using the route command.

As long as you're aware of these potential difficulties, they shouldn't pose any major problems.

Discovering Broadcast-Capable Interfaces at Runtime

Another option for broadcasting when you don't know the correct address in advance is to identify the host's subnet-directed broadcast address at runtime. If the host has more than one Ethernet card or other network interface, there may be several such addresses, and a general-purpose application could try to detect them all.

The process is conceptually simple. First, query the operating system for the list of all active network interfaces. The list will return both those that have broadcast capability, such as Ethernet cards, and those that don't, such as the loopback interface and point-to-point connections set up via serial lines. The next step is to fetch the interface's "flags," a bitmask of attributes that describes its properties, including whether it is broadcast capable. Use the flags to select interfaces that can do broadcasts, and then ask the operating system for the selected interfaces' broadcast addresses. See Table 20.1 for a list of common flags.

Let's look at this process in more detail. The operating system allows you to fetch and manipulate network interfaces by invoking a series of ioctl() calls. The first argument to ioctl() must be an open socket. The second is a function code selected from the list in Table 20.2.

The majority of these ioctl() functions are supported on all operating systems. For example, the SIOCGIFCONF ioctl() returns the list of all active interfaces, and SIOCGIFBRDADDR returns the broadcast address of a particular interface. Information about the selected interface is passed between your program and the operating system via a packed binary structure provided as ioctl() 's third argument. The interface- related function codes use two data types: the ifreq structure is used to pass information about a specific interface back and forth, and the ifconf structure is used when fetching the list of all active interfaces.

Table 20.1. Interface Flags
Flag Description
IFF_ALLMULTI Interface can accept all multicast packets.
IFF_AUTOMEDIA Interface can autoselect its media (e.g., 10bT vs 10b2).
IFF_BROADCAST Interface is broadcast capable.
IFF_DEBUG Interface is in debug mode.
IFF_LOOPBACK Interface is a loopback device.
IFF_MASTER Interface is the master of a load-balancing router.
IFF_MULTICAST Interface can accept multicasts.
IFF_NOARP Interface doesn't do address resolution protocol.
IFF_NOTRAILERS Avoid use of low-level packet trailers .
IFF_POINTOPOINT Interface is a point-to-point device.
IFF_PORTSEL Interface can set media type.
IFF_PROMISC Interface is in promiscuous mode (receives all packets).
IFF_RUNNING Interface's driver is loaded and initialized .
IFF_SLAVE Interface is the slave of a load-balancing router.
IFF_UP Interface is active.

A brief example will make this clearer. Say we already know that we have an Ethernet interface named tu0 (this corresponds to a "Tulip" 100bT Ethernet interface on a Digital Tru64 UNIX box). We can fetch its broadcast address with the following fragment of code:

 my $ifreq = pack('Z16 x16','tu0'); ioctl($sock,SIOCGIFBRDADDR,$ifreq); my ($name,$family,$addr) = unpack('Z16 s x2 a4',$ifreq); print "broadcast = ",inet_ntoa($addr),"\n"; 

We pack the name of the interface card into an ifreq structure, and pass it to ioctl() using the SIOCGIFBRDADDR function code. ioctl() returns its result in $ifreq , which we unpack and display. In order for this to work, we need to know the value of the SIOCGIFBRDADDR constant and the magic formats to use for packing and unpacking the ifreq structure. We discuss the source of this information in the next section.

Table 20.2. ioctl() Function Codes for Fetching Interface Information
Code Argument Description
SIOCGIFCONF ifconf Fetch list of interfaces.
SIOCGIFADDR ifreq Get IP address of interface.
SIOCGIFBRDADDR ifreq Get broadcast address of interface.
SIOCGIFNETMASK ifreq Get netmask of interface.
SIOCGIRDSTADDR ifreq Get destination address of a point-to-point interface.
SIOCGIFHWADDR ifreq Get hardware address of interface.
SIOCGIFFLAGS ifreq Get attributes of interface.

You can pass any open socket to the interface-related ioctl calls, even one that you created for another purpose. In a typical broadcast application, you would create an unconnected UDP socket, query it for the broadcast addresses, and then call send() on the socket to initiate the broadcast.

The IO::Interface Module

This section develops a code module called IO::Interface. When loaded, it adds several methods to the IO::Socket class that you can use to fetch information about the host's network interfaces. The methods this module adds to IO::Socket are as follows :

@interface_names = $socket->if_list

Returns the list of interface names as an array of strings. Only interfaces whose drivers are loaded will be returned.

$flags = $socket->if_flags($interface_name)

Returns the flags for the interface named by $interface_name . The flags are a bitmask of various attributes that indicate , among other things, whether the interface is running and whether it is broadcast capable. Table 20.1 lists the most common flags.

$addr = $socket->if_addr($interface_name)

Returns the (unicast) IP address for the specified interface in dotted -quad form.

$broadcast_addr = $socket->if_broadcast($interface_name)

Returns the broadcast address for the specified interface in dotted-quad form. For interfaces that can't broadcast, returns undef .

$netmask = $socket->if_netmask($interface_name) Returns the netmask for the specified interface.

$dstaddr = $socket->if_dstaddr($interface_name)

For point-to-point interfaces, such as those using SLIP or PPP, returns the IP address of the remote end of the connection. For interfaces that are not point-to-point, returns undef .

$hwaddr = $socket->if_hwaddr($interface_name)

Returns the interface's 6-byte Ethernet hardware address in the following form: aa:bb:cc:dd:ee:ff . Many operating systems do not support the underlying ioctl() function code, in which case if_hwaddr() returns undef . Also returns undef for non-Ethernet interfaces.

Loading IO::Interface with the import tag :flags imports a set of constants to use with the bitmask returned by if_flags() . You can AND these constants with the flags in order to discover whether an interface supports a particular attribute.

This fully functional example shows how to discover whether the Ethernet interface tu0 is up and running:

 #!/usr/bin/perl use IO::Socket; use IO::Interface ':flags'; my $socket = IO::Socket::INET->new(Proto=>'udp') or die; my $flags = $socket->if_flags('tu0') or die; print $flags & IFF_UP ? "Interface is up\n" : "Interface is down\n"; 

And here is our desired function to determine the host's subnet-directed broadcast address(es) at runtime. It takes a socket as argument, calls if_list() to get the list of all interfaces, queries each one in turn to find those that are broadcast capable, and then calls if_broadcast() to get the address itself. The function returns a list of all valid broadcast addresses in dotted-quad form.

 sub get_broadcast_addr {    my $sock = shift;    my   @baddr;    for my $if ($sock->if_list) {       next unless $sock->if_flags($if) & IFF_BROADCAST;       push @baddr,$sock->if_broadcast($if);    }    return @baddr; } 

Another feature of IO::Interface allows you to use it in a function-oriented fashion. If you load IO::Interface with the import tag :functions , it imports the methods just described into the caller in the form of function calls. This allows you to use the calls with ordinary socket handles if you prefer:

 use Socket; use IO::Interface ':functions'; socket(Sock,AF_INET,SOCK_DGRAM,scalar getprotobyname('udp')); @interfaces = if_list(\*SOCK); 

You may use a typeglob reference as shown, or a glob.

IO::Interface Walkthrough

Before we walk through IO::Interface, there is a major caveat. The ioctl() function codes vary tremendously from operating system to operating system and are variously defined in the system header files net/if.h , sys/socket.h , and sys/sockio.h . Before you can use IO::Interface, you must convert the system header files into Perl .ph files using the h2ph tool described in Chapter 17 (Implementing sockatmark() ). However, as you recall, h2ph is far from perfect and the generated files usually need hand tweaking before they will compile and load correctly. [1]

[1] In one case, I had to comment out a subroutine inexplicably named __foo_bar() in order to get the .ph file to load; in another, I deleted several functions that appeared to be defined in terms of themselves !

As a practical alternative to this implementation of IO::Interface, I strongly recommend using a C-language extension by the same name that I developed during the course of researching this chapter. Provided that your operating system has a C or C++ compiler, you can download this module from CPAN and install it with little trouble. In addition to providing all the functionality of the pure-Perl implementation, the C extension has the ability to change interface settings. For example, you can use the module to change the IP address assigned to an Ethernet card. You will find this module on CPAN. [2]

[2] Another CPAN module, Net::Interface, also provides this functionality, but does not seem to be maintained and won't compile under recent versions of Perl.

Nevertheless, it is educational to walk through the pure-Perl version of IO::Interface to get a feel for how to write an interface to a fairly low-level part of the operating system. Figure 20.3 shows the code for the module.

Figure 20.3. The IO::Interface module

graphics/20fig03.gif

Lines 1 “21: Set up the module The first third of the module is Perl paperwork. We bring in the Exporter module, declare exported variables , and create the module's export tags. The only non- boilerplate part of the module is this line:

 use Config; 

which brings in Perl's Config module. This module exports a hash named %Config , which contains information on a variety of architecture-specific data types, including the size of pointers and integers. We need this information to figure out the formats to pack and unpack the data type, including the size of pointers and integers. We need this information to figure out the formats pack and unpack the data passed to the interface ioctls .

Lines 22 “28: Bring in socket and interface libraries We load the Socket library and import its inet_ntoa() function. The reason for using require rather than use to load the module is to avoid a number of irritating warnings that result from prototype conflicts between constants defined in the .ph files and the same constants loaded from Socket.

 require Socket; Socket->import('inet_ntoa'); 

We now load the .ph files that contain constants for dealing with network interfaces. The net/if.ph file contains definitions for the data structures used by the calls to ioctl() . We need it chiefly to get at constants that determine the size of these structures. Next we load the sys/ioctl.ph and sys/sockio.ph files. On some systems, the interface function codes are all defined in the first file, but on others, you must load both files. We load the first file, check to see whether we've gotten the SIOCGIFCONF function code; if not, we proceed to load the second.

 require "net/if.ph"; require "sys/ioctl.ph"; require "sys/sockio.ph" unless defined &SIOCGIFCONF; 

We now take advantage of a little-known feature of Perl's .ph system. Many ioctl() function contain embedded codes the size of the data structures they operate on. When the C compiler evaluates the include files, it is able to determine the size of these structures at compile time and generate the correct constants; but Perl knows nothing about C data structures and needs some help from the programmer to tell it their sizes.

This is what the %sizeof hash is for. Whenever a .ph file needs the size of a data structure, it indexes into this hash. For example, it calls $sizeof{'int'} when it needs the size of an integer, and $sizeof{'struct ifreq'} to fetch the size of the ifreq structure. To get the right values for SIOCGIFCONF and friends , we must set up %sizeof before calling any of the ioctl() function codes. This is done here:

 %sizeof = ('struct ifconf' => 2 * $Config{ptrsize},            'struct ifreq'  => 2 * IFNAMSIZ); 

As it happens, there are only two C data structures that we need to worry about: the ifreq structure, which contains information about a particular interface, and the ifconf structure, which is used to fetch the list of all running interfaces. The ifconf structure is the simpler of the two. It consists of an integer and a pointer. The pointer designates a region of memory to receive the list of interface names, and the integer indicates the size of the region.

The sizes of integers and pointers vary from architecture to architecture, but we can determine them at runtime using Perl's %Config array. Naively, you might guess the size of struct ifconf to be the size of an integer plus the size of a pointer ”but you'd be wrong on some occasions. Most architectures have alignment constraints that force pointers to begin at memory locations that are even multiples of the pointer size. If the integer and pointer sizes are not the same (as is the case on some 64-bit systems), the C compiler will place padding after the integer in order to align the pointer at its natural boundary. This means that the size of the ifconf structure ends up being two pointers' worth, or 2 * $Config{ptrsize} .

The ifreq structure is a C "union," meaning that the same region of memory is used in several ways depending on the context. The first half of the data structure holds the name of the interface and is defined to be IFNAMSIZ bytes long (16 bytes in most implementations ). The second half variously contains

  • a socket address, consisting of a 2-byte address family and the 4-byte IP address separated by 2 bytes of padding.

  • an Ethernet hardware address, consisting of a 2-byte address family and up to 6 bytes of hardware address.

  • interface flags, consisting of 2 bytes of flag data.

  • the name of a "slave" interface, used in various load-balancing schemes (that we won't get into here). The slave name is again IFNAMSIZ bytes long.

A C union is as large as necessary to hold all its variants. In this case, this is IFNAMSIZ repeated twice, or 2 * IFNAMSIZ . With the %sizeof hash initialized correctly, the ioctl() function codes evaluate to their proper values.

As an aside, it took me several iterations and several small test C programs to figure all this out. Although I was happy to get it to work, the fact that it required an intimate knowledge of the internal workings of the C compiler is disappointing.

Lines 29 “34: Define pack() and unpack() formats for the ioctls We will be moving data in and out of the ifreq structure using the pack() and unpack() functions. We now define formats for each of the ifreq variants we will use. We turn the IFNAMSIZ and IFHWADDRLEN constants into variables that we can use in double-quoted strings. Not all operating systems define IFHWADDRLEN , in which case we default to the size of the Ethernet hardware address.

IFREQ_NAME is used for packing the interface name into the structure. It consists of a string IFNAMSIZ bytes long. If the string doesn't fill the available space, it is null- padded using the Z format. The bottom half of the data structure is initialized with IFNAMSIZ bytes of nulls using the x format.

IFREQ_ADDR is used to retrieve interface IP addresses of various types. It consists of the interface name, a 2-byte integer containing address family, 2 bytes of padding, and a 4-byte character string corresponding to the IP address.

IFREQ_ETHER is used to unpack the Ethernet address. In this variant, ifreq contains the interface name, a 2-byte integer containing the address family (which is usually AF_UNSPECIFIED ), and 6 unsigned bytes of address information.

IFREQ_FLAG is the simplest. It consists of the interface name followed by a short integer containing the interface flags.

Lines 35 “38: Attach the IO::Interface methods to IO::Socket The next bit of code exports the various subroutines defined in IO::Interface to the IO::Socket namespace, turning them into methods that can be used with IO::Socket objects. For each of the functions defined in the @functions global, we do an assignment like this one:

 *{"IO\:\:Socket\:\:if_addr"} = \&if_addr; 

This idiom is an obscure way to create an alias in another module's namespace corresponding to a function defined in the current one. It is documented, albeit scantily, in the perlsub and perlref manual pages and is the basic operation performed by Exporter module. We need to do this for a whole list of functions, so we have a little loop that creates the namespace aliases on each function in turn:

 {   no strict 'refs';   *{"IO\:\:Socket\:\:$_"} = \&$_ foreach @functions; } 

no strict 'refs' temporarily turns off the checks that prevent this type of namespace manipulation.

Lines 39 “45: Get the interface address At last we're ready to do real work. The if_addr() function takes a socket and the name of an interface and returns the interface's IP address as a dotted-quad string. We begin by creating a packed ifreq structure containing the name of the requested interface. The IFREQ_NAME format packs the name into the first IFNAMSIZ bytes of the structure, and zeroes out the rest of it. We now call ioctl() using the SIOCGIFADDR command, passing the socket and the newly created ifreq structure. If the ioctl() fails for some reason, we return undef .

Otherwise, ioctl() has returned the requested information in the ifreq structure. We unpack $ifreq using the IFREQ_ADDR format. This returns the name of the interface, its address family, and the address itself. We ignore the other values, but turn the address into dotted-quad form using inet_ntoa() and return it to the caller.

Lines 46 “77: Fetch broadcast address, destination address, hardware address, and netmask The next few functions are very similar, but instead of asking the operating system for the address of the interface, they use different ioctl() function codes to fetch the broadcast address, point-to-point destination address, hardware address, and netmask.

We perform a little bit of extra work in several of these routines in order to prevent the function from returning the address 0.0.0.0, a behavior I discovered on some Linux systems when querying interfaces that don't support a particular type of addressing (for example, asking the loopback interface for its broadcast address). It's better to return undef when there is no address than to return a nonsensical one.

Lines 78 “84: Return interface flags The if_flags() function initializes an ifreq structure with the name of the desired interface, and passes it to ioctl() with the SIOCGIFFLAGS command. If successful, we unpack the result using the IFREQ_FLAGS format and return the flags to the caller.

Lines 85 “100: Fetch list of all interfaces The if_list() function, which returns all active network interfaces, is the most complex of the bunch. We will create a packed ifconf structure consisting of a pointer to a buffer and the buffer length. The buffer is initially empty (filled with zeros) but will be populated after the ioctl() call with an array of ifreq structures, each containing the name of a different interface.

We'll need to make a data structure large enough to hold as many interfaces as we're likely to see. We create a local variable filled with zeroes that is large enough to hold information on 20 interfaces.

We need a format to pack the ifconf structure. Because of alignment constraints, this format will be different on machines whose pointers are 32 bits and those with 64-bit architectures. In the first case, the format is simply "ip" , for an integer followed by a pointer. In the second case, the format is "ix4p" , for an integer, 4 bytes of padding, and a pointer.

We create the ifconf structure by invoking pack() with the buffer length and the buffer itself. The " p " format takes care of incorporating the memory address of the buffer into the packed structure. We now call ioctl() with the SIOCGIFCONF function code to populate the buffer with the interface names. In case of failure, we return undef .

Otherwise, we unpack $ifclist to recover the buffer size. This tells us how much of the buffer the operating system used to store its results. We now step through the buffer, calling substr() to extract one ifreq segment after another. For each segment, we unpack the interface name and stuff it into a hash, using the IFREQ_NAME format defined earlier.

After the loop is finished, we return the sorted keys of the hash. I added this step to the process after I discovered that some operating systems return the same interface multiple times in response to the SIOCGIFCONF request. Stuffing the interface names into a hash forces the list to contain only unique names.


   
Top


Network Programming with Perl
Network Programming with Perl
ISBN: 0201615711
EAN: 2147483647
Year: 2000
Pages: 173

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net