Blame src/n-dhcp4/src/util/socket.c

Packit Service dff8e4
/*
Packit Service dff8e4
 * Socket Utilities
Packit Service dff8e4
 */
Packit Service dff8e4
Packit Service dff8e4
#include <assert.h>
Packit Service dff8e4
#include <c-stdaux.h>
Packit Service dff8e4
#include <errno.h>
Packit Service dff8e4
#include <net/if.h>
Packit Service dff8e4
#include <stdlib.h>
Packit Service dff8e4
#include <string.h>
Packit Service dff8e4
#include <sys/ioctl.h>
Packit Service dff8e4
#include <sys/socket.h>
Packit Service dff8e4
#include "socket.h"
Packit Service dff8e4
Packit Service dff8e4
/**
Packit Service dff8e4
 * socket_SIOCGIFNAME() - resolve an ifindex to an ifname
Packit Service dff8e4
 * @socket:                     socket to operate on
Packit Service dff8e4
 * @ifindex:                    index of network interface to resolve
Packit Service dff8e4
 * @ifname:                     buffer to store resolved name
Packit Service dff8e4
 *
Packit Service dff8e4
 * This uses the SIOCGIFNAME ioctl to resolve an ifindex to an ifname. The
Packit Service dff8e4
 * buffer provided in @ifnamep must be at least IFNAMSIZ bytes in size. The
Packit Service dff8e4
 * maximum ifname length is IFNAMSIZ-1, and this function always
Packit Service dff8e4
 * zero-terminates the result.
Packit Service dff8e4
 *
Packit Service dff8e4
 * This function is similar to if_indextoname(3) provided by glibc, but it
Packit Service dff8e4
 * allows to specify the target socket explicitly. This allows the caller to
Packit Service dff8e4
 * control the target network-namespace, rather than relying on the network
Packit Service dff8e4
 * namespace of the running process.
Packit Service dff8e4
 *
Packit Service dff8e4
 * Return: 0 on success, negative kernel error code on failure.
Packit Service dff8e4
 */
Packit Service dff8e4
int socket_SIOCGIFNAME(int socket, int ifindex, char (*ifnamep)[IFNAMSIZ]) {
Packit Service dff8e4
        struct ifreq req = { .ifr_ifindex = ifindex };
Packit Service dff8e4
        int r;
Packit Service dff8e4
Packit Service dff8e4
        r = ioctl(socket, SIOCGIFNAME, &req;;
Packit Service dff8e4
        if (r < 0)
Packit Service dff8e4
                return -errno;
Packit Service dff8e4
Packit Service dff8e4
        /*
Packit Service dff8e4
         * The linux kernel guarantees that an interface name is always
Packit Service dff8e4
         * zero-terminated, and it always fully fits into IFNAMSIZ bytes,
Packit Service dff8e4
         * including the zero-terminator.
Packit Service dff8e4
         */
Packit Service dff8e4
        memcpy(ifnamep, req.ifr_name, IFNAMSIZ);
Packit Service dff8e4
        return 0;
Packit Service dff8e4
}
Packit Service dff8e4
Packit Service dff8e4
/**
Packit Service dff8e4
 * socket_bind_if() - bind socket to a network interface
Packit Service dff8e4
 * @socket:                     socket to operate on
Packit Service dff8e4
 * @ifindex:                    index of network interface to bind to, or 0
Packit Service dff8e4
 *
Packit Service dff8e4
 * This binds the socket given via @socket to the network interface specified
Packit Service dff8e4
 * via @ifindex. It uses the underlying SO_BINDTODEVICE ioctl of the linux
Packit Service dff8e4
 * kernel. However, if available, if prefers the newer SO_BINDTOIFINDEX ioctl,
Packit Service dff8e4
 * which avoids resolving the interface name temporarily, and thus does not
Packit Service dff8e4
 * suffer from a race-condition.
Packit Service dff8e4
 *
Packit Service dff8e4
 * Return: 0 on success, negative error code on failure.
Packit Service dff8e4
 */
Packit Service dff8e4
int socket_bind_if(int socket, int ifindex) {
Packit Service dff8e4
        char ifname[IFNAMSIZ] = {};
Packit Service dff8e4
        int r;
Packit Service dff8e4
Packit Service dff8e4
        c_assert(ifindex >= 0);
Packit Service dff8e4
Packit Service dff8e4
        /*
Packit Service dff8e4
         * We first try the newer SO_BINDTOIFINDEX. If it is not available on
Packit Service dff8e4
         * the running kernel, we fall back to SO_BINDTODEVICE. This, however,
Packit Service dff8e4
         * requires us to first resolve the ifindex to an ifname. Note that
Packit Service dff8e4
         * this is racy, since the device name might theoretically change
Packit Service dff8e4
         * asynchronously.
Packit Service dff8e4
         *
Packit Service dff8e4
         * Using 0 as ifindex will remove the device-binding. For
Packit Service dff8e4
         * SO_BINDTOIFINDEX we simply pass-through the 0 to the kernel, which
Packit Service dff8e4
         * recognizes this correctly. For SO_BINDTODEVICE we pass the empty
Packit Service dff8e4
         * string, which the kernel recognizes as a request to remove the
Packit Service dff8e4
         * binding.
Packit Service dff8e4
         *
Packit Service dff8e4
         * The commit introducing SO_BINDTOIFINDEX first appeared in linux-5.1:
Packit Service dff8e4
         *
Packit Service dff8e4
         *     commit f5dd3d0c9638a9d9a02b5964c4ad636f06cf7e2c
Packit Service dff8e4
         *     Author: David Herrmann <dh.herrmann@gmail.com>
Packit Service dff8e4
         *     Date:   Tue Jan 15 14:42:14 2019 +0100
Packit Service dff8e4
         *
Packit Service dff8e4
         *         net: introduce SO_BINDTOIFINDEX sockopt
Packit Service dff8e4
         *
Packit Service dff8e4
         * In older kernels, setsockopt(2) is guaranteed to return ENOPROTOOPT
Packit Service dff8e4
         * for this ioctl.
Packit Service dff8e4
         */
Packit Service dff8e4
Packit Service dff8e4
#ifdef SO_BINDTOIFINDEX
Packit Service dff8e4
        r = setsockopt(socket,
Packit Service dff8e4
                       SOL_SOCKET,
Packit Service dff8e4
                       SO_BINDTOIFINDEX,
Packit Service dff8e4
                       &ifindex,
Packit Service dff8e4
                       sizeof(ifindex));
Packit Service dff8e4
        if (r >= 0)
Packit Service dff8e4
                return 0;
Packit Service dff8e4
        else if (errno != ENOPROTOOPT)
Packit Service dff8e4
                return -errno;
Packit Service dff8e4
#endif /* SO_BINDTOIFINDEX */
Packit Service dff8e4
Packit Service dff8e4
        if (ifindex > 0) {
Packit Service dff8e4
                r = socket_SIOCGIFNAME(socket, ifindex, &ifname);
Packit Service dff8e4
                if (r)
Packit Service dff8e4
                        return r;
Packit Service dff8e4
        }
Packit Service dff8e4
Packit Service dff8e4
        r = setsockopt(socket,
Packit Service dff8e4
                       SOL_SOCKET,
Packit Service dff8e4
                       SO_BINDTODEVICE,
Packit Service dff8e4
                       ifname,
Packit Service dff8e4
                       strlen(ifname));
Packit Service dff8e4
        if (r < 0)
Packit Service dff8e4
                return -errno;
Packit Service dff8e4
Packit Service dff8e4
        return 0;
Packit Service dff8e4
}