Skip to content

Instantly share code, notes, and snippets.

@clayfreeman
Last active April 17, 2025 22:26
Show Gist options
  • Save clayfreeman/f5e17339307aa5c70ab1dfa257fed20f to your computer and use it in GitHub Desktop.
Save clayfreeman/f5e17339307aa5c70ab1dfa257fed20f to your computer and use it in GitHub Desktop.
Bind to a random source address before connecting to a remote host via IPv6
/**
* @file
* Bind to a random source address before connecting to a remote host via IPv6.
*
* 1. Install dependencies: `libcap-dev libnl-3-dev libnl-route-3-dev`
* 2. Compile with:
* ```
* gcc -shared -fPIC -Wall -I /usr/include/libnl3 ... -lcap -lnl-3 -lnl-route-3
* ```
*
* 3. Set up your environment:
* ```
* LD_PRELOAD=/path/to/random_in6_addr_connect.so
*
* RANDOM_IPV6_PREFIX=fe80::/64
* RANDOM_IPV6_DEV=eth0
* ```
*
* 4. You may need `setcap cap_net_admin=eip /path/to/program` for non-root.
* 5. A random source address will now be used for each connection.
*/
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <dlfcn.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <netlink/addr.h>
#define INET6_CIDRSTRLEN INET6_ADDRSTRLEN + 4
static int add_in6_address(const struct in6_addr *const addr)
{
const char *const dev = getenv("RANDOM_IPV6_DEV");
if (!dev)
{
fprintf(stderr, "WARN: Missing RANDOM_IPV6_DEV environment variable.\n");
return 0;
}
struct nl_sock *sock = nl_socket_alloc();
if (!sock)
return 0;
if (nl_connect(sock, NETLINK_ROUTE) != 0)
{
nl_socket_free(sock);
return 0;
}
struct nl_cache *link_cache = NULL;
if (rtnl_link_alloc_cache(sock, AF_UNSPEC, &link_cache) != 0)
{
fprintf(stderr, "WARN: Could not allocate link cache.\n");
nl_socket_free(sock);
return 0;
}
int ifindex = rtnl_link_name2i(link_cache, dev);
if (ifindex == 0)
{
fprintf(stderr, "WARN: Could not resolve ifindex for %s\n", dev);
nl_cache_free(link_cache);
nl_socket_free(sock);
return 0;
}
struct rtnl_addr *rta = rtnl_addr_alloc();
if (!rta)
{
nl_cache_free(link_cache);
nl_socket_free(sock);
return 0;
}
struct nl_addr *local = nl_addr_build(AF_INET6, addr, sizeof(*addr));
if (!local)
{
rtnl_addr_put(rta);
nl_cache_free(link_cache);
nl_socket_free(sock);
return 0;
}
nl_addr_set_prefixlen(local, 128);
rtnl_addr_set_local(rta, local);
rtnl_addr_set_ifindex(rta, ifindex);
rtnl_addr_set_flags(rta, IFA_F_NODAD);
rtnl_addr_set_preferred_lifetime(rta, 0);
rtnl_addr_set_valid_lifetime(rta, 3600);
int err = rtnl_addr_add(sock, rta, 0);
if (err != 0)
{
fprintf(stderr, "WARN: rtnl_addr_add failed: %s\n", nl_geterror(err));
}
nl_addr_put(local);
rtnl_addr_put(rta);
nl_cache_free(link_cache);
nl_socket_free(sock);
return err == 0;
}
static int get_random_in6_addr(struct in6_addr *const addr)
{
const char *const env = getenv("RANDOM_IPV6_PREFIX");
if (!env)
{
fprintf(stderr, "WARN: Missing RANDOM_IPV6_PREFIX environment variable. Set this to the prefix that should be used for generating random source addresses.\n");
return 0;
}
char *const buffer = calloc(INET6_CIDRSTRLEN, sizeof(char));
if (!buffer)
{
fprintf(stderr, "WARN: Could not allocate temporary buffer\n");
return 0;
}
strncpy(buffer, env, INET6_CIDRSTRLEN - 1);
char *const slash = strchr(buffer, '/');
if (!slash)
{
fprintf(stderr, "WARN: RANDOM_IPV6_PREFIX must be a valid IPv6 address in CIDR notation (e.g., fe80::/64).\n");
free(buffer);
return 0;
}
*slash = '\0';
const char *const prefix_str = slash + 1;
int result = inet_pton(AF_INET6, buffer, addr);
if (result != 1)
{
fprintf(stderr, "WARN: RANDOM_IPV6_PREFIX must be a valid IPv6 address in CIDR notation (e.g., fe80::/64).\n");
free(buffer);
return 0;
}
size_t prefix_len;
result = sscanf(prefix_str, "%zu", &prefix_len);
free(buffer);
if (result != 1 || prefix_len > 128)
{
fprintf(stderr, "WARN: RANDOM_IPV6_PREFIX must be a valid IPv6 address in CIDR notation (e.g., fe80::/64).\n");
return 0;
}
size_t host_bytes = (128 - prefix_len) / 8;
size_t host_bits = (128 - prefix_len) % 8;
struct in6_addr hostmask = {0};
struct in6_addr random;
size_t offset = sizeof(hostmask) - host_bytes;
size_t length = sizeof(hostmask) - offset;
if (host_bits)
{
hostmask.s6_addr[offset - 1] = ~(0xFF << host_bits);
}
if (length)
{
memset(&hostmask.s6_addr[offset], 0xFF, length);
}
getrandom(&random, sizeof(random), 0);
for (size_t i = 0; i < sizeof(hostmask); ++i)
{
addr->s6_addr[i] &= ~hostmask.s6_addr[i];
addr->s6_addr[i] |= (hostmask.s6_addr[i] & random.s6_addr[i]);
}
return 1;
}
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
static int (*connect)(int, const struct sockaddr *, socklen_t) = NULL;
if (!connect)
{
connect = dlsym(RTLD_NEXT, "connect");
if (!connect)
{
fprintf(stderr, "ERROR: Failed to link to connect(): %s\n", dlerror());
exit(EXIT_FAILURE);
}
}
if (addr && addr->sa_family == AF_INET6)
{
int optval = 1;
if (setsockopt(sockfd, SOL_IPV6, IPV6_FREEBIND, &optval, sizeof(optval)) == 0)
{
struct sockaddr_in6 rand_src = {0};
rand_src.sin6_family = AF_INET6;
if (get_random_in6_addr(&rand_src.sin6_addr) && add_in6_address(&rand_src.sin6_addr))
{
if (bind(sockfd, (struct sockaddr *)&rand_src, sizeof(rand_src)) != 0)
{
perror("WARN: Unable to bind to random source address");
}
}
}
}
return connect(sockfd, addr, addrlen);
}
int setuid(uid_t uid)
{
static int (*setuid)(uid_t) = NULL;
if (!setuid)
{
setuid = dlsym(RTLD_NEXT, "setuid");
if (!setuid)
{
fprintf(stderr, "ERROR: Failed to link to setuid(): %s\n", dlerror());
exit(EXIT_FAILURE);
}
}
if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) != 0)
{
perror("ERROR: prctl(PR_SET_KEEPCAPS, 1)");
exit(EXIT_FAILURE);
}
int result = setuid(uid);
cap_t caps = cap_get_proc();
if (caps == NULL)
{
perror("ERROR: cap_get_proc()");
exit(EXIT_FAILURE);
}
if (cap_clear(caps) != 0)
{
perror("ERROR: cap_clear()");
cap_free(caps);
exit(EXIT_FAILURE);
}
cap_value_t cap_net_admin = CAP_NET_ADMIN;
if (cap_set_flag(caps, CAP_PERMITTED, 1, &cap_net_admin, CAP_SET) != 0)
{
perror("ERROR: cap_set_flag(CAP_PERMITTED, 1, CAP_NET_ADMIN, CAP_SET)");
cap_free(caps);
exit(EXIT_FAILURE);
}
if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap_net_admin, CAP_SET) != 0)
{
perror("ERROR: cap_set_flag(CAP_EFFECTIVE, 1, CAP_NET_ADMIN, CAP_SET)");
cap_free(caps);
exit(EXIT_FAILURE);
}
if (cap_set_proc(caps) != 0)
{
perror("ERROR: cap_set_proc()");
cap_free(caps);
exit(EXIT_FAILURE);
}
cap_free(caps);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment