Mock getaddrinfo
parent
76f34954a4
commit
da3cd101c6
18
Makefile
18
Makefile
|
@ -2,7 +2,7 @@ ROOT_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
|||
BUILD_DIR := $(ROOT_DIR)/build
|
||||
|
||||
export RBENV_ROOT ?= $(ROOT_DIR)/build/rbenv
|
||||
RBENV_VERSION := v1.2.0
|
||||
_RBENV_VERSION := v1.2.0
|
||||
RUBY_BUILD_VERSION = v20220218
|
||||
|
||||
OPENSSL_1_0_VERSION := 1.0.2j
|
||||
|
@ -39,7 +39,7 @@ clean:
|
|||
rm -rf build/
|
||||
|
||||
$(RBENV_ROOT)/:
|
||||
git clone https://github.com/rbenv/rbenv/ "$@" -b "$(RBENV_VERSION)" --depth 1
|
||||
git clone https://github.com/rbenv/rbenv/ "$@" -b "$(_RBENV_VERSION)" --depth 1
|
||||
$(RBENV_ROOT)/plugins/ruby-build/: | $(RBENV_ROOT)/
|
||||
git clone https://github.com/rbenv/ruby-build/ "$@" -b "$(RUBY_BUILD_VERSION)" --depth 1
|
||||
rbenv: | $(RBENV_ROOT)/plugins/ruby-build/
|
||||
|
@ -107,16 +107,18 @@ ruby-1.0: $(RBENV_ROOT)/versions/$(RUBY_1_0_VERSION)-cryptcheck
|
|||
ruby-1.1: $(RBENV_ROOT)/versions/$(RUBY_1_1_VERSION)-cryptcheck
|
||||
ruby: ruby-1.0 ruby-1.1
|
||||
|
||||
build/libfaketime.so: spec/faketime/faketime.c spec/faketime/faketime.h
|
||||
$(CC) $^ -o "$@" -shared -fPIC -ldl -std=c99 -Werror -Wall
|
||||
faketime: build/libfaketime.so
|
||||
.PHONY: faketime
|
||||
build/libfake.so: spec/fake/fake.c spec/fake/fake.h
|
||||
LANG=C $(CC) $^ -g -o "$@" -shared -fPIC -ldl -std=c99 -Werror -Wall -pedantic
|
||||
fake: build/libfake.so
|
||||
|
||||
build/test: spec/fake/test.c
|
||||
LANG=C $(CC) $^ -g -o "$@" -Werror -Wall -pedantic
|
||||
|
||||
test-material:
|
||||
bin/generate-test-material.rb
|
||||
|
||||
test: spec/faketime/libfaketime.so
|
||||
LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0):$(BUILD_DIR)" bin/rspec
|
||||
test: build/libfake.so
|
||||
LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0):$(BUILD_DIR)" LD_PRELOAD="$(ROOT_DIR)/$^" bin/rspec
|
||||
.PHONY: test
|
||||
|
||||
docker-1.0:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
require 'thor'
|
||||
require 'awesome_print'
|
||||
require 'amazing_print'
|
||||
begin
|
||||
require 'pry-byebug'
|
||||
rescue LoadError
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
require 'ffi'
|
||||
|
||||
module Fake
|
||||
extend FFI::Library
|
||||
ffi_lib 'fake'
|
||||
|
||||
def self.freeze(time)
|
||||
self._freeze time.to_i
|
||||
return yield
|
||||
ensure
|
||||
self._unfreeze
|
||||
end
|
||||
require 'amazing_print'
|
||||
|
||||
def self.getaddrinfo(host, *ips)
|
||||
args = ips.collect { |ip| [:string, ip] }
|
||||
self._mock_getaddrinfo host, ips.size, *args.flatten
|
||||
return yield
|
||||
ensure
|
||||
self._unmock_getaddrinfo
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self._freeze(_)
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
|
||||
def self._unfreeze
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
|
||||
attach_function :_freeze, [:time_t], :void
|
||||
attach_function :_unfreeze, [], :void
|
||||
|
||||
def self._mock_getaddrinfo(_, _, *_)
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
|
||||
def self._unmock_getaddrinfo
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
|
||||
attach_function :_mock_getaddrinfo, [:string, :size_t, :varargs], :void
|
||||
attach_function :_unmock_getaddrinfo, [], :void
|
||||
end
|
|
@ -0,0 +1,216 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include <stdbool.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "fake.h"
|
||||
|
||||
bool frozen = false;
|
||||
time_t frozen_time = 0;
|
||||
static time_t (*libc_time)(time_t*);
|
||||
|
||||
void _freeze(const time_t time) {
|
||||
frozen_time = time;
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
void _unfreeze() {
|
||||
frozen = false;
|
||||
}
|
||||
|
||||
time_t time(time_t *arg) {
|
||||
if (frozen) {
|
||||
if (arg) *arg = frozen_time;
|
||||
return frozen_time;
|
||||
} else {
|
||||
time_t time = libc_time(arg);
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct ip_t {
|
||||
int af;
|
||||
void *addr;
|
||||
} ip_t;
|
||||
typedef struct host_t {
|
||||
char *host;
|
||||
size_t size;
|
||||
ip_t **ips;
|
||||
} host_t;
|
||||
host_t *mocked_host = NULL;
|
||||
|
||||
static int (*libc_getaddrinfo)(const char*, const char*,
|
||||
const struct addrinfo *, struct addrinfo **);
|
||||
|
||||
static void free_mocked_host() {
|
||||
if (mocked_host == NULL) return;
|
||||
for (size_t n =0; n < mocked_host->size; ++n ) {
|
||||
ip_t *ip = mocked_host->ips[n];
|
||||
free(ip->addr);
|
||||
free(ip);
|
||||
}
|
||||
free(mocked_host->ips);
|
||||
free(mocked_host->host);
|
||||
free(mocked_host);
|
||||
mocked_host = NULL;
|
||||
}
|
||||
|
||||
void _mock_getaddrinfo(const char *host, const size_t size, ...) {
|
||||
free_mocked_host();
|
||||
|
||||
host_t *mocked = malloc(sizeof(*mocked));
|
||||
mocked->host = strdup(host);
|
||||
mocked->size = size;
|
||||
ip_t **ips = malloc(size * sizeof(*ips));
|
||||
|
||||
va_list args;
|
||||
va_start(args, size);
|
||||
|
||||
for (size_t n = 0; n < size; ++n) {
|
||||
char *arg = va_arg(args, char*);
|
||||
ip_t *ip = malloc(sizeof(*ip));
|
||||
int family;
|
||||
void *addr, *saddr;
|
||||
if (strstr(arg, ".") != NULL) {
|
||||
family = AF_INET;
|
||||
struct sockaddr_in *s4 = malloc(sizeof(*s4));
|
||||
s4->sin_family = family;
|
||||
s4->sin_port = 0;
|
||||
saddr = s4;
|
||||
addr = &(s4->sin_addr);
|
||||
} else if (strstr(arg, ":") != NULL) {
|
||||
family = AF_INET6;
|
||||
struct sockaddr_in6 *s6 = malloc(sizeof(*s6));
|
||||
s6->sin6_family = family;
|
||||
s6->sin6_port = 0;
|
||||
s6->sin6_flowinfo = 0;
|
||||
s6->sin6_scope_id = 0;
|
||||
saddr = s6;
|
||||
addr = &(s6->sin6_addr);
|
||||
} else {
|
||||
fprintf(stderr, "Invalid IP format: %s\n", arg);
|
||||
exit(-1);
|
||||
}
|
||||
inet_pton(family, arg, addr);
|
||||
|
||||
ip->af = family;
|
||||
ip->addr = saddr;
|
||||
|
||||
ips[n] = ip;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
|
||||
mocked->ips = ips;
|
||||
mocked_host = mocked;
|
||||
}
|
||||
|
||||
void _unmock_getaddrinfo() {
|
||||
free_mocked_host();
|
||||
}
|
||||
|
||||
typedef struct socktype_t {
|
||||
int type;
|
||||
int protocol;
|
||||
} socktype_t;
|
||||
const socktype_t SOCK_TYPES[] = {
|
||||
{ .type = SOCK_STREAM, .protocol = IPPROTO_TCP },
|
||||
{ .type = SOCK_DGRAM, .protocol = IPPROTO_UDP },
|
||||
{ .type = SOCK_RAW, .protocol = IPPROTO_IP }
|
||||
};
|
||||
const size_t SOCK_TYPES_SIZE = sizeof(SOCK_TYPES)/sizeof(socktype_t);
|
||||
|
||||
int get_port_from_service(const char *service) {
|
||||
if (service == NULL) return 0;
|
||||
|
||||
struct servent* servent = getservbyname(service, NULL);
|
||||
int port;
|
||||
if (servent == NULL) {
|
||||
port = htons(atoi(service));
|
||||
} else {
|
||||
port = servent->s_port;
|
||||
free(servent);
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
int getaddrinfo(const char *node, const char *service,
|
||||
const struct addrinfo *hints, struct addrinfo **res) {
|
||||
const int port = get_port_from_service(service);
|
||||
|
||||
if (mocked_host == NULL || strcmp(node, mocked_host->host) != 0)
|
||||
return libc_getaddrinfo(node, service, hints, res);
|
||||
|
||||
size_t size = mocked_host->size;
|
||||
struct addrinfo *addr = NULL, *previous = NULL;
|
||||
for (size_t n = 0; n < size; ++n) {
|
||||
const ip_t *ip = mocked_host->ips[n];
|
||||
|
||||
for ( size_t m = 0; m < SOCK_TYPES_SIZE; ++m) {
|
||||
const socktype_t type = SOCK_TYPES[m];
|
||||
|
||||
if ( (hints->ai_socktype != 0 && hints->ai_socktype != type.type) ||
|
||||
(hints->ai_protocol != 0 && hints->ai_protocol != type.protocol) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addr = malloc(sizeof(*addr));
|
||||
addr->ai_flags = hints->ai_flags;
|
||||
addr->ai_family = ip->af;
|
||||
addr->ai_socktype = type.type;
|
||||
addr->ai_protocol = type.protocol;
|
||||
|
||||
size_t len;
|
||||
void *ai_addr;
|
||||
switch (addr->ai_family) {
|
||||
case AF_INET:;
|
||||
struct sockaddr_in *ai_addr4;
|
||||
len = sizeof(*ai_addr4);
|
||||
ai_addr4 = malloc(len);
|
||||
ai_addr = ai_addr4;
|
||||
memcpy(ai_addr4, ip->addr, len);
|
||||
ai_addr4->sin_port = port;
|
||||
break;
|
||||
case AF_INET6:;
|
||||
struct sockaddr_in6 *ai_addr6;
|
||||
len = sizeof(*ai_addr6);
|
||||
ai_addr6 = malloc(len);
|
||||
ai_addr = ai_addr6;
|
||||
memcpy(ai_addr6, ip->addr, len);
|
||||
ai_addr6->sin6_port = port;
|
||||
break;
|
||||
}
|
||||
addr->ai_addrlen = len;
|
||||
addr->ai_addr = ai_addr;
|
||||
addr->ai_canonname = NULL;
|
||||
addr->ai_next = previous;
|
||||
previous = addr;
|
||||
}
|
||||
}
|
||||
|
||||
*res = addr;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __attribute__((constructor)) setup() {
|
||||
char *error = dlerror();
|
||||
*(void **) (&libc_time) = dlsym(RTLD_NEXT, "time");
|
||||
if ((error = dlerror()) != NULL) {
|
||||
fprintf(stderr, "%s\n", error);
|
||||
}
|
||||
*(void **) (&libc_getaddrinfo) = dlsym(RTLD_NEXT, "getaddrinfo");
|
||||
if ((error = dlerror()) != NULL) {
|
||||
fprintf(stderr, "%s\n", error);
|
||||
}
|
||||
}
|
||||
|
||||
static void __attribute__((destructor)) teardown() {
|
||||
free_mocked_host();
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#include <time.h>
|
||||
|
||||
void _freeze(const time_t time);
|
||||
void _unfreeze();
|
||||
|
||||
void _mock_getaddrinfo(const char *host, const size_t size, ...);
|
||||
void _unmock_getaddrinfo();
|
|
@ -0,0 +1,82 @@
|
|||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
int debug(const char *fmt, ...) {
|
||||
// return 0;
|
||||
va_list arg;
|
||||
va_start(arg, fmt);
|
||||
int result = vfprintf(stderr, fmt, arg);
|
||||
va_end(arg);
|
||||
fflush(stderr);
|
||||
return result;
|
||||
}
|
||||
|
||||
void debug_saddr(const int family, const void *saddr) {
|
||||
char buff[INET6_ADDRSTRLEN];
|
||||
const void *addr;
|
||||
switch (family) {
|
||||
case AF_INET:;
|
||||
const struct sockaddr_in *s4 = saddr;
|
||||
debug(" family: %d\n", s4->sin_family);
|
||||
debug(" port: %d\n", s4->sin_port);
|
||||
addr = &(s4->sin_addr);
|
||||
break;
|
||||
case AF_INET6:;
|
||||
const struct sockaddr_in6 *s6 = saddr;
|
||||
debug(" family: %d\n", s6->sin6_family);
|
||||
debug(" port: %d\n", s6->sin6_port);
|
||||
debug(" flowinfo: %d\n", s6->sin6_flowinfo);
|
||||
debug(" scope: %d\n", s6->sin6_scope_id);
|
||||
addr = &(s6->sin6_addr);
|
||||
break;
|
||||
}
|
||||
|
||||
inet_ntop(family, addr, buff, INET6_ADDRSTRLEN);
|
||||
debug(" addr: %p, %s\n", saddr, buff);
|
||||
}
|
||||
|
||||
void debug_addr(const struct addrinfo *addr) {
|
||||
debug("addrinfo: %p\n", addr);
|
||||
debug(" flags: %d\n", addr->ai_flags);
|
||||
debug(" family: %d\n", addr->ai_family);
|
||||
debug(" type: %d\n", addr->ai_socktype);
|
||||
debug(" protocol: %d\n", addr->ai_protocol);
|
||||
debug(" len: %d\n", addr->ai_addrlen);
|
||||
if (addr->ai_addrlen > 0) debug_saddr(addr->ai_family, addr->ai_addr);
|
||||
debug(" name: %s\n", addr->ai_canonname);
|
||||
debug(" next: %p\n", addr->ai_next);
|
||||
}
|
||||
|
||||
void debug_addrinfo(const struct addrinfo *addr) {
|
||||
for (const struct addrinfo *rp = addr; rp != NULL; rp = rp->ai_next) {
|
||||
debug_addr(rp);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(struct addrinfo));
|
||||
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
|
||||
hints.ai_socktype = SOCK_STREAM; /* Datagram socket */
|
||||
hints.ai_flags = 0; /* For wildcard IP address */
|
||||
hints.ai_protocol = 0; /* Any protocol */
|
||||
hints.ai_canonname = NULL;
|
||||
hints.ai_addr = NULL;
|
||||
hints.ai_next = NULL;
|
||||
|
||||
struct addrinfo *result;
|
||||
debug("Before result: result=%p\n", &result);
|
||||
const int s = getaddrinfo("localhost", NULL, &hints, &result);
|
||||
if (s != 0) {
|
||||
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
debug("The result: result=%p\n", result);
|
||||
debug_addrinfo(result);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
require 'ffi'
|
||||
|
||||
module FakeTime
|
||||
extend FFI::Library
|
||||
ffi_lib 'faketime'
|
||||
|
||||
def self._freeze(_)
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
def self.unfreeze
|
||||
#This is a stub, used for indexing
|
||||
end
|
||||
|
||||
attach_function :_freeze, [:ulong], :void
|
||||
attach_function :unfreeze, [], :void
|
||||
|
||||
def self.freeze(time)
|
||||
self._freeze time.to_i
|
||||
if block_given?
|
||||
begin
|
||||
return yield
|
||||
ensure
|
||||
self.unfreeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,35 +0,0 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <dlfcn.h>
|
||||
#include "time.h"
|
||||
#include "faketime.h"
|
||||
|
||||
char frozen = 0;
|
||||
time_t frozen_time = 0;
|
||||
|
||||
typedef time_t (*orig_time_f_type)(time_t*);
|
||||
orig_time_f_type orig_time = NULL;
|
||||
|
||||
void _freeze(unsigned long time) {
|
||||
frozen_time = (time_t)time;
|
||||
frozen = 1;
|
||||
}
|
||||
|
||||
void unfreeze() {
|
||||
frozen = 0;
|
||||
}
|
||||
|
||||
time_t time(time_t *arg) {
|
||||
if (orig_time == NULL) {
|
||||
orig_time = (orig_time_f_type) dlsym(RTLD_NEXT, "time");
|
||||
}
|
||||
|
||||
if (frozen) {
|
||||
if (arg) {
|
||||
*arg = frozen_time;
|
||||
}
|
||||
return frozen_time;
|
||||
} else {
|
||||
time_t time = orig_time(arg);
|
||||
return time;
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
void _freeze(unsigned long time);
|
||||
void unfreeze();
|
Binary file not shown.
Loading…
Reference in New Issue