Go to main content

Oracle® Solaris 11.4 Programming Interfaces Guide

Exit Print View

Updated: November 2020
 
 

Advanced Socket Topics

For most programmers, the mechanisms already described are enough to build distributed applications. This section describes additional features.

Out-of-Band Data

The stream socket abstraction includes out-of-band data. Out-of-band data is a logically independent transmission channel between a pair of connected stream sockets. Out-of-band data is delivered independent of normal data. The out-of-band data facilities must support the reliable delivery of at least one out-of-band message at a time. This message can contain at least one byte of data. At least one message can be pending delivery at any time.

With in-band signaling, urgent data is delivered in sequence with normal data, and the message is extracted from the normal data stream. The extracted message is stored separately. Users can choose between receiving the urgent data in order and receiving the data out of sequence, without having to buffer the intervening data.

Using MSG_PEEK, you can peek at out-of-band data. If the socket has a process group, a SIGURG signal is generated when the protocol is notified of its existence. A process can set the process group or process ID to deliver SIGURG to with the appropriate fcntl call, as described in Interrupt-Driven Socket I/O for SIGIO. If multiple sockets have out-of-band data waiting for delivery, a select call for exceptional conditions can determine which sockets have such data pending. For more information, see the fcntl(2) and select(3C) man pages.

A logical mark is placed in the data stream at the point at which the out-of-band data was sent. The remote login and remote shell applications use this facility to propagate signals between client and server processes. When a signal is received, all data up to the mark in the data stream is discarded.

To send an out-of-band message, apply the MSG_OOB flag to send() or sendto(). To receive out-of-band data, specify MSG_OOB to recvfrom() or recv(). If out-of-band data is taken in line the MSG_OOB flag is not needed. For more information, see the send(3C), sendto(3C), recvfrom(3C), and recv(3C) man pages.

The SIOCATMARK ioctl(2) indicates whether the read pointer currently points at the mark in the data stream.

int yes;
ioctl(s, SIOCATMARK, &yes);

If yes is 1 on return, the next read returns data after the mark. Otherwise, assuming out-of-band data has arrived, the next read provides data sent by the client before sending the out-of-band signal. The routine in the remote login process that flushes output on receipt of an interrupt or quit signal is shown in the following example. This code reads the normal data up to the mark to discard the normal data, then reads the out-of-band byte.

A process can also read or peek at the out-of-band data without first reading up to the mark. Accessing this data when the underlying protocol delivers the urgent data in-band with the normal data, and sends notification of its presence only ahead of time, is more difficult. An example of this type of protocol is TCP, the protocol used to provide socket streams in the Internet family. With such protocols, the out-of-band byte might not yet have arrived when recv() is called with the MSG_OOB flag. In that case, the call returns the error of EWOULDBLOCK. Also, the amount of in-band data in the input buffer might cause normal flow control to prevent the peer from sending the urgent data until the buffer is cleared. The process must then read enough of the queued data to clear the input buffer before the peer can send the urgent data.

Example 30  Flushing Terminal I/O on Receipt of Out-of-Band Data
#include <sys/ioctl.h>
#include <sys/file.h>
...
oob()
{
    int out = FWRITE;
    char waste[BUFSIZ];
    int mark = 0;
 
    /* flush local terminal output */
    ioctl(1, TIOCFLUSH, (char *) &out);
    while(1) {
        if (ioctl(rem, SIOCATMARK, &mark) == -1) {
            perror("ioctl");
            break;
        }
        if (mark)
            break;
        (void) read(rem, waste, sizeof waste);
    }
    if (recv(rem, &mark, 1, MSG_OOB) == -1) {
        perror("recv");
        ...
    }
    ...
}

A facility to retain the position of urgent in-line data in the socket stream is available as a socket-level option, SO_OOBINLINE. For more information, see the getsockopt(3C) man page. With this socket-level option, the position of urgent data remains. However, the urgent data immediately following the mark in the normal data stream is returned without the MSG_OOB flag. Reception of multiple urgent indications moves the mark, but does not lose any out-of-band data.

Nonblocking Sockets

Some applications require sockets that do not block. For example, a server would return an error code, not executing a request that cannot complete immediately. This error could cause the process to be suspended, awaiting completion. After creating and connecting a socket, issuing a fcntl(2) call, as shown in the following example, makes the socket nonblocking.

Example 31  Setting a Nonblocking Socket
#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL, 0) == -1) {
    perror("fcntl F_GETFL");
    exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY) == -1) {
    perror("fcntl F_SETFL, FNDELAY");
    exit(1);
}

When performing I/O on a nonblocking socket, check for the error EWOULDBLOCK in errno.h, which occurs when an operation would normally block. accept(), connect(), send(), recv(), read(), and write() can all return EWOULDBLOCK. If an operation such as a send() cannot be done in its entirety but partial writes work, as when using a stream socket, all available data is processed. The return value is the amount of data actually sent. For more information, see the accept(3C), connect(3C), send(3C), read(2), write(2), and recv(3C) man pages.

Asynchronous Socket I/O

Asynchronous communication between processes is required in applications that simultaneously handle multiple requests. Asynchronous sockets must be of the SOCK_STREAM type. To make a socket asynchronous, you issue a fcntl(2) call, as shown in the following example.

Example 32  Making a Socket Asynchronous
#include <fcntl.h>
#include <sys/file.h>
...
int fileflags;
int s;
...
s = socket(AF_INET6, SOCK_STREAM, 0);
...
if (fileflags = fcntl(s, F_GETFL ) == -1) {
    perror("fcntl F_GETFL");
    exit(1);
}
if (fcntl(s, F_SETFL, fileflags | FNDELAY | FASYNC) == -1) {
    perror("fcntl F_SETFL, FNDELAY | FASYNC");
    exit(1);
}

After sockets are initialized, connected, and configured as nonblocking and asynchronous, communication is similar to reading and writing a file asynchronously. Initiate a data transfer by using send(), write(), recv(), or read(). For more information, see the send(3C), write(2), recv(3C), and read(2) man pages. A signal-driven I/O routine completes a data transfer, as described in the next section.

Interrupt-Driven Socket I/O

    The SIGIO signal notifies a process when a socket, or any file descriptor, has finished a data transfer. The steps in using SIGIO are as follows:

  1. Set up a SIGIO signal handler with the signal() or sigvec() calls. For more information, see the signal(3C) man page.

  2. Use fcntl() to set the process ID or process group ID to route the signal to its own process ID or process group ID. The default process group of a socket is group 0. For more information, see the fcntl(2) man page.

  3. Convert the socket to asynchronous, as shown in Asynchronous Socket I/O.

The following sample code enables receipt of information about pending requests as the requests occur for a socket by a given process. With the addition of a handler for SIGURG, this code can also be used to prepare for receipt of SIGURG signals.

Example 33  Enabling Asynchronous Notification of I/O Requests
#include <fcntl.h>
#include <sys/file.h>
 ...
signal(SIGIO, io_handler);
/* Set the process receiving SIGIO/SIGURG signals to us. */
if (fcntl(s, F_SETOWN, getpid()) < 0) {
    perror("fcntl F_SETOWN");
    exit(1);
}

Signals and Process Group ID

For SIGURG and SIGIO, each socket has a process number and a process group ID. These values are initialized to zero, but can be redefined at a later time with the F_SETOWN fcntl() command, as in the previous example. A positive third argument to fcntl sets the socket's process ID. A negative third argument to fcntl() sets the socket's process group ID. The only allowed recipient of SIGURG and SIGIO signals is the calling process. A similar fcntl(), F_GETOWN, returns the process number of a socket. For more information, see the fcntl(2) man page.

You can also enable reception of SIGURG and SIGIO by using ioctl() to assign the socket to the user's process group. For more information, see the ioctl(2) man page.

/* oobdata is the out-of-band data handling routine */
sigset(SIGURG, oobdata);
int pid = -getpid();
if (ioctl(client, SIOCSPGRP, (char *) &pid) < 0) {
    perror("ioctl: SIOCSPGRP");
}

Selecting Specific Protocols

If the third argument of the socket call is 0, socket selects a default protocol to use with the returned socket of the type requested. The default protocol is usually correct, and alternate choices are not usually available. When using raw sockets to communicate directly with lower-level protocols or lower-level hardware interfaces, set up de-multiplexing with the protocol argument. For more information, see the socket(3C) man page.

Using raw sockets in the Internet family to implement a new protocol on IP ensures that the socket only receives packets for the specified protocol. To obtain a particular protocol, determine the protocol number as defined in the protocol family. For the Internet family, use one of the library routines that are discussed in Standard Routines, such as getprotobyname.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
...
pp = getprotobyname("newtcp");
s = socket(AF_INET6, SOCK_STREAM, pp->p_proto);

Using getprotobyname() results in a socket s by using a stream-based connection, but with a protocol type of newtcp instead of the default tcp.

Address Binding

    For addressing, TCP and UDP use a 4-tuple of:

  • Local IP address

  • Local port number

  • Foreign IP address

  • Foreign port number

TCP requires these 4-tuples to be unique. UDP does not. User programs do not always know proper values to use for the local address and local port, because a host can reside on multiple networks. The set of allocated port numbers is not directly accessible to a user. To avoid these problems, leave parts of the address unspecified and let the system assign the parts appropriately when needed. Various portions of these tuples can be specified by various parts of the sockets API:

bind()

Local address or local port or both. For more information, see the bind(3C) man page.

connect()

Foreign address and foreign port. For more information, see the connect(3C) man page.

A call to accept retrieves connection information from a foreign client. This causes the local address and port to be specified to the system even though the caller of accept did not specify anything. The foreign address and foreign port are returned.

A call to listen can cause a local port to be chosen. If no explicit bind has been done to assign local information, listen assigns an ephemeral port number.

A service that resides at a particular port can bind to that port. Such a service can leave the local address unspecified if the service does not require local address information. The local address is set to in6addr_any, a variable with a constant value in <netinet/in.h>. If the local port does not need to be fixed, a call to listen causes a port to be chosen. Specifying an address of in6addr_any or a port number of 0 is known as wildcarding. For AF_INET, INADDR_ANY is used in place of in6addr_any. For more information, see the bind(3C) and listen(3C) man pages.

The wildcard address simplifies local address binding in the Internet family. The following sample code binds a specific port number that was returned by a call to getaddrinfo to a socket and leaves the local address unspecified:

#include <sys/types.h>
#include <netinet/in.h>
...
struct addrinfo  *aip;
...
if (bind(sock, aip->ai_addr, aip->ai_addrlen) == -1) {
    perror("bind");
    (void) close(sock);
    return (-1);
}

Each network interface on a host typically has a unique IP address. Sockets with wildcard local addresses can receive messages that are directed to the specified port number. Messages that are sent to any of the possible addresses that are assigned to a host are also received by sockets with wildcard local addresses. To allow only hosts on a specific network to connect to the server, a server binds the address of the interface on the appropriate network.

Similarly, a local port number can be left unspecified, in which case the system selects a port number. For example, to bind a specific local address to a socket, but to leave the local port number unspecified, you could use bind as follows:

bzero (&sin, sizeof (sin));
(void) inet_pton (AF_INET6, "::ffff:127.0.0.1", sin.sin6_addr.s6_addr);
sin.sin6_family = AF_INET6;
sin.sin6_port = htons(0);
bind(s, (struct sockaddr *) &sin, sizeof sin);

The system uses two criteria to select the local port number:

  • Internet port numbers less than 1024 (IPPORT_RESERVED) are reserved for privileged users. Nonprivileged users can use any Internet port number that is greater than 1024. The largest Internet port number is 65535.

  • The port number is not currently bound to some other socket.

The port number and IP address of the client are found through accept() or getpeername(). For more information, see the accept(3C) and getpeername(3C) man pages.

In certain cases, the algorithm used by the system to select port numbers is unsuitable for an application due to the two-step creation process for associations. For example, the Internet file transfer protocol specifies that data connections must always originate from the same local port. Duplicate associations are avoided by connecting to different foreign ports. In this situation, the system would disallow binding the same local address and local port number to a socket if a previous data connection's socket still existed.

To override the default port selection algorithm, you must perform an option call before address binding:

int on = 1;
...
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
bind(s, (struct sockaddr *) &sin, sizeof sin);

With this call, local addresses already in use can be bound. This binding does not violate the uniqueness requirement. The system still verifies at connect time that any other sockets with the same local address and local port do not have the same foreign address and foreign port. If the association already exists, the error EADDRINUSE is returned.

Socket Options

You can set and get several options on sockets through setsockopt() and getsockopt(). For example, you can change the send or receive buffer space. For more information, see the setsockopt(3C) and getsockopt(3C) man pages.

The general forms of the calls are in the following formats:

setsockopt(s, level, optname, optval, optlen);

getsockopt(s, level, optname, optval, optlen);

The operating system can adjust the values appropriately at any time.

The arguments of setsockopt and getsockopt calls are in the following list:

s

Socket on which the option is to be applied

level

Specifies the protocol level, such as socket level, indicated by the symbolic constant SOL_SOCKET in sys/socket.h

optname

Symbolic constant defined in sys/socket.h that specifies the option

optval

Points to the value of the option

optlen

Points to the length of the value of the option

For getsockopt, optlen is a value-result argument. This argument is initially set to the size of the storage area pointed to by optval. On return, the argument's value is set to the length of storage used.

When a program needs to determine an existing socket's type, the program should invoke inetd by using the SO_TYPE socket option and the getsockopt call:

#include <sys/types.h>
#include <sys/socket.h>
 
int type, size;
 
size = sizeof (int);
if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *) &type, &size) < 0) {
    ...
}

After getsockopt, type is set to the value of the socket type, as defined in sys/socket.h. For a datagram socket, type would be SOCK_DGRAM.

Socket Level Properties

Starting with the Oracle Solaris 11.2 release, you can use the SO_FLOW_SLA option to set the service-level properties for the socket. A socket application using the SO_FLOW_SLA socket option causes the system to create a system flow, which is an enforcement mechanism for the service-level properties. You can use flowadm to observe the system flows. For more information, see the flowadm(8) man page. These system flows have the prefix <id>.sys.sock.

The pfiles prints the SO_FLOW_SLA socket option with other socket options. For more information, see the pfiles(1) man page.


Note -  You can set the socket level properties only for TCP and UDP sockets.

The usage of SO_FLOW_SLA socket option is described in the following example.

#include <sys/types.h>
#include <sys/socket.h>

extern struct sockaddr *serv_addr;

int fd;
mac_flow_props_t mprop;
mac_flow_props_t mprop_result;

fd = socket(AF_INET, SOCK_STREAM, 0);

mprop.mfp_version = MAC_FLOW_PROP_VERSION1;
mprop.mfp_mask = MFP_MAXBW | MFP_PRIORITY;
mprop.mfp_priority = MFP_PRIO_HIGH;
mprop.mfp_maxbw = 100000000;           /* in bits per second */
setsockopt(fd, SOL_SOCKET, SO_FLOW_SLA, &mprop, sizeof (mprop));

connect(fd, serv_addr, sizeof(*serv_addr));

getsockopt(fd, SOL_SOCKET, SO_FLOW_SLA, &mprop_result, sizeof (mprop_result));

In the example, the TCP client socket is created along with the system flow. The flow is set to a high priority and the maximum bandwidth is set to 100Mbps.

The system flow is created for the socket by calling connect() or accept() functions after setsockopt. If either accept() or connect() function is already called, setting SO_FLOW_SLA will create a flow. Properties of the flow are set according to the values specified in mac_flow_props_t structure. This structure is passed as a pointer to setsockopt as an optval argument. You can know the status of the flow creation by using getsockopt. The status is stored in the mprop_result.mfp_status field. The mac_flow_props_t structure is defined as follows.

typedef struct mac_flow_props_s {
int		mfp_version;
uint32_t   mfp_mask;
int		mfp_priority;   /* flow priority */
uint64_t   mfp_maxbw;	  /* bandwidth limit in bps */
int		mfp_status;	 /* flow create status for getsockopt */
} mac_flow_props_t;

The following list describes the fields of the mac_flow_props_t structure.

mfp_version

Denotes the version of the mac_flow_props_t structure. Currently, mfp_version can only be set to 1.

#define MAC_FLOW_PROP_VERSION1
mfp_mask

Denotes the bit mask values. The following bit mask values are valid.

  • MRP_MAXBW

  • MRP_PRIORITY

mfp_priority

Denotes the priority of processing the packets that belong to the socket. The following priority values are valid.

  • MFP_PRIO_NORMAL

  • MFP_PRIO_HIGH

mfp_maxbw

Denotes the maximum bandwidth allotted to the socket in bits per second. Value of 0 means all the packets of socket must be dropped.

mfp_status

Denotes the status of the flow creation. You can obtain the status of flow creation by calling getsockopt. getsockopt sets the mfp_status field. A value of 0 means a flow is successfully created. In case of an error, this field is set to one of the following error codes.

  • EPERM: No Privilege.

  • ENOTCONN: If the call is made before the application does a connect or bind.

  • EOPNOTSUPP: Flow creation is not supported for this socket.

  • EALREADY: Flow with identical attributes exists.

  • EINPROGRESS: Flow is being created.

inetd Daemon

The inetd daemon is invoked at startup time and is now configured by using smf. The configuration was previously performed by /etc/inet/inetd.conf file. For more information, see the inetd(8), and smf(7) man pages.

Use inetconv to convert the configuration file content into SMF format services, and then manage these services using inetadm and svcadm. For more information, see the inetconv(8), inetadm(8) and svcadm(8) man pages.

The inetd daemon polls each socket, waiting for a connection request to the service corresponding to that socket. For SOCK_STREAM type sockets, inetd accepts using accept() on the listening socket, forks using fork(), duplicates using dup() the new socket to file descriptors 0 and 1 (stdin and stdout), closes other open file descriptors, and executes using exec the appropriate server. For more information, see the accept(3C), fork(2), dup(2), and exec(2) man page.

The primary benefit of using inetd is that services not in use do not consume system resources. A secondary benefit is that inetd does most of the work to establish a connection. The server started by inetd has the socket connected to its client on file descriptors 0 and 1. The server can immediately read, write, send, or receive. Servers can use buffered I/O as provided by the stdio conventions, as long as the servers use fflush when appropriate. For more information, see the fflush(3C) man page.

The getpeername() routine returns the address of the peer (process) connected to a socket. This routine is useful in servers started by inetd. For example, you could use this routine to log the Internet address such as fec0::56:a00:20ff:fe7d:3dd2, which is conventional for representing the IPv6 address of a client. An inetd server could use the following sample code:

struct sockaddr_storage name;
int namelen = sizeof (name);
char abuf[INET6_ADDRSTRLEN];
struct in6_addr addr6;
struct in_addr addr;

if (getpeername(fd, (struct sockaddr *) &name, &namelen) == -1) {
    perror("getpeername");
    exit(1);
} else {
    addr = ((struct sockaddr_in *)&name)->sin_addr;
    addr6 = ((struct sockaddr_in6 *)&name)->sin6_addr;
    if (name.ss_family == AF_INET) {
            (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
    } else if (name.ss_family == AF_INET6 &&
               IN6_IS_ADDR_V4MAPPED(&addr6)) {
            /* this is a IPv4-mapped IPv6 address */
            IN6_MAPPED_TO_IN(&addr6, &addr);
            (void) inet_ntop(AF_INET, &addr, abuf, sizeof (abuf));
    } else if (name.ss_family == AF_INET6) {
            (void) inet_ntop(AF_INET6, &addr6, abuf, sizeof (abuf));

    }
    syslog("Connection from %s\n", abuf);
}

Broadcasting and Determining Network Configuration

Broadcasting is not supported in IPv6. Broadcasting is supported only in IPv4.

Messages sent by datagram sockets can be broadcast to reach all of the hosts on an attached network. The network must support broadcast because the system provides no simulation of broadcast in software. Broadcast messages can place a high load on a network because broadcast messages force every host on the network to service the broadcast messages. Broadcasting is usually used for either of two reasons:

  • To find a resource on a local network without having its address

  • For functions that require information to be sent to all accessible neighbors

To send a broadcast message, create an Internet datagram socket:

s = socket(AF_INET, SOCK_DGRAM, 0);

Bind a port number to the socket:

sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = htons(MYPORT);
bind(s, (struct sockaddr *) &sin, sizeof sin);

The datagram can be broadcast on only one network by sending to the network's broadcast address. A datagram can also be broadcast on all attached networks by sending to the special address INADDR_BROADCAST, which is defined in netinet/in.h.

The system provides a mechanism to determine a number of pieces of information about the network interfaces on the system. This information includes the IP address and broadcast address. The SIOCGIFCONF ioctl() call returns the interface configuration of a host in a single ifconf structure. This structure contains an array of ifreq structures. Every address family supported by every network interface to which the host is connected has its own ifreq structure. For more information, see the ioctl(2) man page.

The following example shows the ifreq structures defined in net/if.h.

Example 34  Showing the Contents of net/if.h
struct ifreq {
    #define IFNAMSIZ 16
    char ifr_name[IFNAMSIZ]; /* if name, e.g., "en0" */
    union {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        char ifru_oname[IFNAMSIZ]; /* other if name */
        struct sockaddr ifru_broadaddr;
        short ifru_flags;
        int ifru_metric;
        char ifru_data[1]; /* interface dependent data */
        char ifru_enaddr[6];
    } ifr_ifru;
    #define ifr_addr ifr_ifru.ifru_addr
    #define ifr_dstaddr ifr_ifru.ifru_dstaddr
    #define ifr_oname ifr_ifru.ifru_oname
    #define ifr_broadaddr ifr_ifru.ifru_broadaddr
    #define ifr_flags ifr_ifru.ifru_flags
    #define ifr_metric ifr_ifru.ifru_metric
    #define ifr_data ifr_ifru.ifru_data
    #define ifr_enaddr ifr_ifru.ifru_enaddr
};

The call that obtains the interface configuration is:

/*
 * Do SIOCGIFNUM ioctl to find the number of interfaces
 *
 * Allocate space for number of interfaces found
 *
 * Do SIOCGIFCONF with allocated buffer
 *
 */
if (ioctl(s, SIOCGIFNUM, (char *)&numifs) == -1) {
    numifs = MAXIFS;
}
bufsize = numifs * sizeof(struct ifreq);
reqbuf = (struct ifreq *)malloc(bufsize);
if (reqbuf == NULL) {
    fprintf(stderr, "out of memory\n");
    exit(1);
}
ifc.ifc_buf = (caddr_t)&reqbuf[0];
ifc.ifc_len = bufsize;
if (ioctl(s, SIOCGIFCONF, (char *)&ifc) == -1) {
    perror("ioctl(SIOCGIFCONF)");
    exit(1);
}

After this call, buf contains an array of ifreq structures. Every network to which the host connects has an associated ifreq structure. The sort order these structures appear in is:

  • Alphabetical by interface name

  • Numerical by supported address families

The value of ifc.ifc_len is set to the number of bytes used by the ifreq structures.

Each structure has a set of interface flags that indicate whether the corresponding network is up or down, point-to-point or broadcast, and so on. The following example shows ioctl() returning the SIOCGIFFLAGS flags for an interface specified by an ifreq structure.

Example 35  Obtaining Interface Flags
struct ifreq *ifr;
ifr = ifc.ifc_req;
for (n = ifc.ifc_len/sizeof (struct ifreq); --n >= 0; ifr++) {
    /*
     * Be careful not to use an interface devoted to an address
     * family other than those intended.
     */
    if (ifr->ifr_addr.sa_family != AF_INET)
        continue;
    if (ioctl(s, SIOCGIFFLAGS, (char *) ifr) < 0) {
        ...
    }
    /* Skip boring cases */
    if ((ifr->ifr_flags & IFF_UP) == 0 ||
            (ifr->ifr_flags & IFF_LOOPBACK) ||
            (ifr->ifr_flags & (IFF_BROADCAST | IFF_POINTOPOINT)) == 0)
        continue;
}

The following example uses the SIOGGIFBRDADDR ioctl command to obtain the broadcast address of an interface.

Example 36  Obtaining the Broadcast Address of an Interface
if (ioctl(s, SIOCGIFBRDADDR, (char *) ifr) < 0) {
    ...
}
memcpy((char *) &dst, (char *) &ifr->ifr_broadaddr,
    sizeof ifr->ifr_broadaddr);

You can also use SIOGGIFBRDADDR ioctl to get the destination address of a point-to-point interface. For more information, see the ioctl(2) man page.

After the interface broadcast address is obtained, transmit the broadcast datagram with sendto:

sendto(s, buf, buflen, 0, (struct sockaddr *)&dst, sizeof dst);

Use one sendto for each interface to which the host is connected, if that interface supports the broadcast or point-to-point addressing. For more information, see the sendto(3C) man page.