Thanks @Andy Dalton for giving this useful information. Based upon that I've made a small piece of code for LD_PRELOAD to implement SO_BINDTODEVICE to every program.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <net/if.h>
#include <string.h>
#include <errno.h>
//Credits go to https://catonmat.net/simple-ld-preload-tutorial and https://catonmat.net/simple-ld-preload-tutorial-part-two
//And of course to https://unix.stackexchange.com/a/648721/334883
//compile with gcc -nostartfiles -fpic -shared bindInterface.c -o bindInterface.so -ldl -D_GNU_SOURCE
//Use with BIND_INTERFACE=<network interface> LD_PRELOAD=./bindInterface.so <your program> like curl ifconfig.me
int socket(int family, int type, int protocol)
{
//printf("MySocket\n"); //"LD_PRELOAD=./bind.so wget -O- ifconfig.me 2> /dev/null" prints two times "MySocket". First is for DNS-Lookup.
//If your first nameserver is not reachable via bound interface,
//then it will try the next nameserver until it succeeds or stops with name resolution error.
//This is why it could take significantly longer than curl --interface wlan0 ifconfig.me
char bind_addr_env;
struct ifreq interface;
int (original_socket)(int, int, int);
original_socket = dlsym(RTLD_NEXT,"socket");
int fd = (int)(original_socket)(family,type,protocol);
bind_addr_env = getenv("BIND_INTERFACE");
int errorCode;
if ( bind_addr_env!= NULL && strlen(bind_addr_env) > 0)
{
//printf(bind_addr_env);
strcpy(interface.ifr_name,bind_addr_env);
errorCode = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &interface, sizeof(interface));
if ( errorCode < 0)
{
perror("setsockopt");
errno = EINVAL;
return -1;
};
}
else
{
printf("Warning: Programm with LD_PRELOAD startet, but BIND_INTERFACE environment variable not set\n");
fprintf(stderr,"Warning: Programm with LD_PRELOAD startet, but BIND_INTERFACE environment variable not set\n");
}
return fd;
}
Compile it with
gcc -nostartfiles -fpic -shared bindInterface.c -o bindInterface.so -ldl -D_GNU_SOURCE
Use it with
BIND_INTERFACE=wlan0 LD_PRELOAD=./bindInterface.so wget -O- ifconfig.me 2>/dev/null
Note: Executing this can take a lot longer than with curl --interface wlan0 ifconfig.me
This is because it also tries to reach your first nameserver from /etc/resolv.conf
also with interface bound to. If this nameserver is not reachable it uses the second one and so on. If you edit /etc/resolv.conf
and put for example Google's public DNS server 8.8.8.8 at first place, it is as fast as the curl version. With --interface
option curl binds only to this interface when making the actual connection, not when resolving IP-address. So when using curl with bound interface and a privacy VPN it will leak DNS requests through normal connection when not properly configured. Use this code for verification:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <dlfcn.h>
#include <net/if.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
int connect (int sockfd, const struct sockaddr addr, socklen_t addrlen)
{
int (original_connect)(int, const struct sockaddr, socklen_t);
original_connect = dlsym(RTLD_NEXT,"connect");
static struct sockaddr_in *socketAddress;
socketAddress = (struct sockaddr_in *)addr;
if (socketAddress -> sin_family == AF_INET)
{
// inet_ntoa(socketAddress->sin_addr.s_addr); when #include <arpa/inet.h> is not included
char *dest = inet_ntoa(socketAddress->sin_addr); //with #include <arpa/inet.h>
printf("connecting to: %s / ",dest);
}
struct ifreq boundInterface =
{
.ifr_name = "none",
};
socklen_t optionlen = sizeof(boundInterface);
int errorCode;
errorCode = getsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, &boundInterface, &optionlen);
if ( errorCode < 0)
{
perror("getsockopt");
return -1;
};
printf("Bound Interface: %s\n",boundInterface.ifr_name);
return (int)original_connect(sockfd, addr, addrlen);
}
Use same option as above for compiling. Use with
LD_PRELOAD=./bindInterface.so curl --interface wlan0 ifconfig.me
connecting to: 192.168.178.1 / Bound Interface: none
connecting to: 34.117.59.81 / Bound Interface: wlan0
185.107.XX.XX
Note 2: Changing /etc/resolv.conf
is not permanent. This is another topic. It was just done for demonstrating why it takes longer.