Mock getaddrinfo

master
aeris 1 year ago
parent 76f34954a4
commit e12c5bbdfb
  1. 18
      Makefile
  2. 2
      bin/cryptcheck
  3. 7
      bin/rspec.sh
  4. 46
      spec/fake.rb
  5. 216
      spec/fake/fake.c
  6. 7
      spec/fake/fake.h
  7. 82
      spec/fake/test.c
  8. 27
      spec/faketime.rb
  9. 35
      spec/faketime/faketime.c
  10. 2
      spec/faketime/faketime.h
  11. BIN
      spec/faketime/libfaketime.so

@ -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,7 @@
#!/usr/bin/env bash
set -e
eval "$(bin/load-rbenv "$1")"
BASE_DIR="$(dirname "$(dirname "$0")")"
shift
LD_PRELOAD="$BASE_DIR/build/libfake.so" LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$BASE_DIR/build/" \
exec rspec "$@"

@ -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…
Cancel
Save