Skip to content

Instantly share code, notes, and snippets.

@aabccd021
Created July 25, 2025 04:22
Show Gist options
  • Save aabccd021/9b9995adafea055aaadff0e7226e1f49 to your computer and use it in GitHub Desktop.
Save aabccd021/9b9995adafea055aaadff0e7226e1f49 to your computer and use it in GitHub Desktop.
Mock domain/host with LD_PRELOAD
#define _GNU_SOURCE
#include <dlfcn.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAX_MAPPINGS 128
#define MAX_LINE 256
typedef struct {
char hostname[128];
char ip[64];
int port;
} hostmap_t;
static hostmap_t mappings[MAX_MAPPINGS];
static int mappings_count = 0;
static int loaded = 0;
void load_mappings() {
if (loaded) return;
loaded = 1;
const char *file = getenv("HOSTMAP_FILE");
if (!file) file = "map.txt";
FILE *f = fopen(file, "r");
if (!f) return;
char line[MAX_LINE];
while (fgets(line, sizeof(line), f)) {
char host[128], ip[64];
int port = 0;
if (sscanf(line, "%127s %63[^:]:%d", host, ip, &port) == 3) {
strncpy(mappings[mappings_count].hostname, host, sizeof(mappings[0].hostname)-1);
strncpy(mappings[mappings_count].ip, ip, sizeof(mappings[0].ip)-1);
mappings[mappings_count].port = port;
mappings_count++;
if (mappings_count >= MAX_MAPPINGS) break;
}
}
fclose(f);
}
hostmap_t *find_mapping_by_hostname(const char *hostname) {
for (int i = 0; i < mappings_count; ++i) {
if (strcmp(mappings[i].hostname, hostname) == 0) {
return &mappings[i];
}
}
return NULL;
}
hostmap_t *find_mapping_by_ip(const char *ip) {
for (int i = 0; i < mappings_count; ++i) {
if (strcmp(mappings[i].ip, ip) == 0) {
return &mappings[i];
}
}
return NULL;
}
// Interpose getaddrinfo
typedef int (*orig_getaddrinfo_f_type)(const char *, const char *, const struct addrinfo *, struct addrinfo **);
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
load_mappings();
static orig_getaddrinfo_f_type orig_getaddrinfo = NULL;
if (!orig_getaddrinfo)
orig_getaddrinfo = (orig_getaddrinfo_f_type)dlsym(RTLD_NEXT, "getaddrinfo");
if (node) {
hostmap_t *mapping = find_mapping_by_hostname(node);
if (mapping) {
// Use mapped IP
return orig_getaddrinfo(mapping->ip, service, hints, res);
}
}
return orig_getaddrinfo(node, service, hints, res);
}
// Interpose connect
typedef int (*orig_connect_f_type)(int, const struct sockaddr *, socklen_t);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
load_mappings();
static orig_connect_f_type orig_connect = NULL;
if (!orig_connect)
orig_connect = (orig_connect_f_type)dlsym(RTLD_NEXT, "connect");
if (addr->sa_family == AF_INET) {
struct sockaddr_in *in_addr = (struct sockaddr_in *)addr;
char ipstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(in_addr->sin_addr), ipstr, sizeof(ipstr));
hostmap_t *mapping = find_mapping_by_ip(ipstr);
if (mapping && ntohs(in_addr->sin_port) == 80) {
struct sockaddr_in new_addr = *in_addr;
new_addr.sin_port = htons(mapping->port);
return orig_connect(sockfd, (struct sockaddr *)&new_addr, addrlen);
}
}
// TODO: add IPv6 support if needed
return orig_connect(sockfd, addr, addrlen);
}
tmpdir=$(mktemp -d)
echo 'fetch("http://example.com").then(response => console.log(response.status))' > "$tmpdir/fetch.js"
echo "example.com 127.0.0.1:4002" > "$tmpdir/hosts"
export HOSTMAP_FILE="$tmpdir/hosts"
nix run nixpkgs#gcc_multi -- -shared -fPIC -o "$tmpdir/libmockhost.so" ./mock_host.c -ldl
timeout 10 nix run nixpkgs#simple-http-server -- --port 4002 >/dev/null 2>&1 &
pid=$!
trap "kill $pid" EXIT
nix shell nixpkgs#nodejs --command sh -c "LD_PRELOAD=$tmpdir/libmockhost.so node fetch.js"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment