morristech
7/9/2019 - 7:32 AM

Force any binary to use a specific network interface in Linux

Force any binary to use a specific network interface in Linux

setnif

Force any binary to use a specific network interface.

Requires gcc and bash. Linux only.

Usage:

$ sudo ./setnif.sh eth1 curl www.google.com

Inspired by Daniel Ryde's Libc wrapper for bind and connect.

#!/usr/bin/env bash

# Build lib
build()
{
	gcc -o $SO -nostartfiles -fpic -shared -xc - -ldl -D_GNU_SOURCE <<EOF
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>

int (*std_socket)( int, int, int );

void _init( void )
{
	const char *err;

	std_socket = dlsym( RTLD_NEXT, "socket" );

	if( (err = dlerror()) != NULL )
		fprintf( stderr, "dlsym (socket): %s\n", err );
}

static void set_nif( int sockfd )
{
	struct ifreq ifr;
	const char *nif;

	if( !(nif = getenv( "NIF" )) )
	{
		fprintf( stderr, "NIF unset\n" );
		return;
	}

	memset( &ifr, 0, sizeof( ifr ) );
	strncat( ifr.ifr_name, nif, sizeof( ifr.ifr_name ) );

	if( ioctl( sockfd, SIOCGIFINDEX, &ifr ) )
	{
		perror( "ioctl" );
		return;
	}

	if( setsockopt(
		sockfd,
		SOL_SOCKET,
		SO_BINDTODEVICE,
		(void *)&ifr,
		sizeof( ifr ) ) < 0 )
		perror( "SO_BINDTODEVICE failed" );
}

int socket( int domain, int type, int protocol )
{
	int sockfd;

	if( (sockfd = std_socket( domain, type, protocol )) > 2 &&
		domain == AF_INET )
		set_nif( sockfd );

	return sockfd;
}
EOF
}

(( $# < 2 )) && {
	echo "usage: ${0##*/} DEVICE BINARY [ARGS]"
	exit 1
}

readonly SO=${SO:-"${0%/*}/setnif.so"}

if ! [ -r $SO ] || find ${SO%/*} -newer $SO -name ${0##*/} &>/dev/null
then
	build || exit 1
fi

export NIF=$1
shift

LD_PRELOAD=$SO $@