Last active
April 17, 2025 22:26
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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