clinuxdnsgetaddrinfo

How to select the interface used to perform a hostname lookup


I am working in an application embedded in a device running Linux and BusyBox. The hardware has 2 communication interfaces: Wi-Fi and 3G.

In each connection, the application must try to connect using wi-fi first and, if it fails, the application tries again using 3G.

I am forcing the connection to use the selected interface binding it like this:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <net/if.h>

static void resolveDns(const char *hostname, struct addrinfo **destInfo)
{
    int err;
    struct addrinfo hints;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((err = getaddrinfo(hostname, "80", &hints, destInfo)) != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(err));
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in *addr = (struct sockaddr_in *)((*destInfo)->ai_addr);
    printf("Destination IP: %s\n", inet_ntoa(addr->sin_addr));
}

static void getComInterface(const char *iface, struct ifreq *ifr)
{
    ifr->ifr_addr.sa_family = AF_INET;
    strcpy(ifr->ifr_name, iface);

    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    int err = ioctl(sock, SIOCGIFADDR, ifr);
    close(sock);

    if (err) {
        fprintf(stderr, "ioctl error: %d\n", err);
        exit(EXIT_FAILURE);
    }

    printf("Interface IP: %s\n", inet_ntoa(((struct sockaddr_in *) &ifr->ifr_addr)->sin_addr));
}

int main()
{
    int err;

    struct ifreq ifr;
    getComInterface("wlan0", &ifr);

    struct addrinfo *destInfo;
    resolveDns("www.google.com", &destInfo);

    int s = socket(AF_INET, SOCK_STREAM, 0);
    err = bind(s, &ifr.ifr_addr, sizeof(ifr.ifr_addr));
    if (err) {
        fprintf(stderr, "bind error = %d, %d\n", err, errno);
        exit(EXIT_FAILURE);
    }

    err = connect(s, destInfo->ai_addr, destInfo->ai_addrlen);
    if (err) {
        fprintf(stderr, "connect error = %d, %d \n", err, errno);
        exit(EXIT_FAILURE);
    }

    printf("Ok!\n");

    freeaddrinfo(destInfo);
    close(s);
    return EXIT_SUCCESS;
}

But this doesn't solve the problem in DNS lookup.

Is there a way to force getaddrinfo to use the selected interface? Or, even better, is there a way to force all connections to use the selected interface without disconnecting the other?

P.S.: If you know how to do this in more complex SO, like Ubuntu for instance, please share your solution.

Thanks


Solution

  • I am afraid there is no way to do it just via the standard C library, i.e. you need to change the default gateway, and set gateway for each connection. Please consider the pseudocode below:

    In this way, you will be able to change the default gateway for new connections but keep for existing ones.

    The change of the routing table can be done via Linux shell commands like ip route etc. they can be launched from the C via system, e.g. system( "ip route show" ); You may also write scripts with more complicated logic and launch them from your C code.

    However, this solution has one flaw, usually, if your current interface has no Internet connection, it means that all the connections using this interface might eventually fail.