diff --git a/Makefile b/Makefile index 2a6aa40..7a8b7e3 100644 --- a/Makefile +++ b/Makefile @@ -48,8 +48,12 @@ LIBGWDNS_TEST_TARGET = $(GWPROXY_DIR)/tests/dns.t LIBGWDNS_TEST_CC_SOURCES = $(GWPROXY_DIR)/tests/dns.c LIBGWDNS_TEST_OBJECTS = $(LIBGWDNS_TEST_CC_SOURCES:%.c=%.c.o) -ALL_TEST_TARGETS = $(LIBGWDNS_TEST_TARGET) $(LIBGWPSOCKS5_TEST_TARGET) -ALL_OBJECTS = $(GWPROXY_OBJECTS) $(LIBGWPSOCKS5_OBJECTS) $(LIBGWDNS_OBJECTS) $(LIBGWDNS_TEST_OBJECTS) $(LIBGWPSOCKS5_TEST_OBJECTS) +LIBGWDNS_CACHE_TEST_TARGET = $(GWPROXY_DIR)/tests/dns_cache.t +LIBGWDNS_CACHE_TEST_CC_SOURCES = $(GWPROXY_DIR)/tests/dns_cache.c +LIBGWDNS_CACHE_TEST_OBJECTS = $(LIBGWDNS_CACHE_TEST_CC_SOURCES:%.c=%.c.o) + +ALL_TEST_TARGETS = $(LIBGWDNS_TEST_TARGET) $(LIBGWDNS_CACHE_TEST_TARGET) $(LIBGWPSOCKS5_TEST_TARGET) +ALL_OBJECTS = $(GWPROXY_OBJECTS) $(LIBGWPSOCKS5_OBJECTS) $(LIBGWDNS_OBJECTS) $(LIBGWDNS_TEST_OBJECTS) $(LIBGWDNS_CACHE_TEST_OBJECTS) $(LIBGWPSOCKS5_TEST_OBJECTS) ALL_TARGETS = $(GWPROXY_TARGET) $(LIBGWPSOCKS5_TARGET) $(LIBGWDNS_TARGET) $(ALL_TEST_TARGETS) ALL_DEPFILES = $(ALL_OBJECTS:.o=.o.d) @@ -72,6 +76,9 @@ $(LIBGWDNS_TARGET): $(LIBGWDNS_OBJECTS) $(LIBGWDNS_TEST_TARGET): $(LIBGWDNS_TEST_OBJECTS) $(LIBGWDNS_TARGET) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +$(LIBGWDNS_CACHE_TEST_TARGET): $(LIBGWDNS_CACHE_TEST_OBJECTS) $(LIBGWDNS_TARGET) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + %.c.o: %.c $(CC) $(CFLAGS) $(DEPFLAGS) -c $< -o $@ @@ -83,10 +90,12 @@ clean: rm -f $(TO_BE_REMOVED) IE=LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(shell pwd) -test: $(LIBGWDNS_TEST_TARGET) $(LIBGWPSOCKS5_TEST_TARGET) +test: $(LIBGWDNS_TEST_TARGET) $(LIBGWDNS_CACHE_TEST_TARGET) $(LIBGWPSOCKS5_TEST_TARGET) @echo "Running tests..."; @echo "Testing libgwdns..."; @$(IE) ./$(LIBGWDNS_TEST_TARGET); + @echo "Testing libgwdns cache..."; + @$(IE) ./$(LIBGWDNS_CACHE_TEST_TARGET); @echo "Testing libgwpsocks5..."; @$(IE) ./$(LIBGWPSOCKS5_TEST_TARGET); @echo "Tests completed successfully."; diff --git a/src/gwproxy/dns.c b/src/gwproxy/dns.c index d7b8025..f7cbcb2 100644 --- a/src/gwproxy/dns.c +++ b/src/gwproxy/dns.c @@ -490,7 +490,7 @@ static void process_queue_entry(struct gwp_dns_ctx *ctx) * clone() for each entry if we can process them in the current * thread. */ - if ((ctx->nr_entries + 16) > ctx->nr_sleeping) + if (ctx->nr_entries > (ctx->nr_sleeping + 16)) process_queue_entry_batch(ctx); else process_queue_entry_single(ctx); diff --git a/src/gwproxy/gwproxy.c b/src/gwproxy/gwproxy.c index 051cf27..2d44c51 100644 --- a/src/gwproxy/gwproxy.c +++ b/src/gwproxy/gwproxy.c @@ -7,6 +7,7 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif +#include #include #include #include @@ -36,7 +37,6 @@ #include #include #include -#include #include #include @@ -410,403 +410,6 @@ static int parse_options(int argc, char *argv[], struct gwp_cfg *cfg) return 0; } -/* - * Advantage of using inline assembly for syscalls: - * - * 1) Avoid the use of `errno`. Mostly it's implemented as - * a function call to `__errno_location()`. - * - * 2) Less register clobberings. x86-64 syscall only clobbers - * `rax`, `rcx`, `r11`, and `memory`. While libc function - * calls clobber `rax`, `rdi`, `rsi`, `rdx`, `r10`, `r8`, - * `r9`, `rcx`, `r11`, and `memory`. - */ -#ifdef __x86_64__ -#define __do_syscall0(NUM) ({ \ - intptr_t rax; \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM) /* %rax */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall1(NUM, ARG1) ({ \ - intptr_t rax; \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1) /* %rdi */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall2(NUM, ARG1, ARG2) ({ \ - intptr_t rax; \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1), /* %rdi */ \ - "S"(ARG2) /* %rsi */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall3(NUM, ARG1, ARG2, ARG3) ({ \ - intptr_t rax; \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1), /* %rdi */ \ - "S"(ARG2), /* %rsi */ \ - "d"(ARG3) /* %rdx */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall4(NUM, ARG1, ARG2, ARG3, ARG4) ({ \ - intptr_t rax; \ - register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1), /* %rdi */ \ - "S"(ARG2), /* %rsi */ \ - "d"(ARG3), /* %rdx */ \ - "r"(__r10) /* %r10 */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall5(NUM, ARG1, ARG2, ARG3, ARG4, ARG5) ({ \ - intptr_t rax; \ - register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ - register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1), /* %rdi */ \ - "S"(ARG2), /* %rsi */ \ - "d"(ARG3), /* %rdx */ \ - "r"(__r10), /* %r10 */ \ - "r"(__r8) /* %r8 */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -#define __do_syscall6(NUM, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \ - intptr_t rax; \ - register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ - register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ - register __typeof__(ARG6) __r9 __asm__("r9") = (ARG6); \ - \ - __asm__ volatile( \ - "syscall" \ - : "=a"(rax) /* %rax */ \ - : "a"(NUM), /* %rax */ \ - "D"(ARG1), /* %rdi */ \ - "S"(ARG2), /* %rsi */ \ - "d"(ARG3), /* %rdx */ \ - "r"(__r10), /* %r10 */ \ - "r"(__r8), /* %r8 */ \ - "r"(__r9) /* %r9 */ \ - : "rcx", "r11", "memory" \ - ); \ - rax; \ -}) - -static inline int __sys_epoll_wait(int epfd, struct epoll_event *events, - int maxevents, int timeout) -{ - return (int) __do_syscall4(__NR_epoll_wait, epfd, events, maxevents, - timeout); -} - -static inline ssize_t __sys_read(int fd, void *buf, size_t len) -{ - return (ssize_t) __do_syscall3(__NR_read, fd, buf, len); -} - -static inline ssize_t __sys_write(int fd, const void *buf, size_t len) -{ - return (ssize_t) __do_syscall3(__NR_write, fd, buf, len); -} - -static inline ssize_t __sys_recvfrom(int sockfd, void *buf, size_t len, - int flags, struct sockaddr *src_addr, - socklen_t *addrlen) -{ - return (ssize_t) __do_syscall6(__NR_recvfrom, sockfd, buf, len, flags, - src_addr, addrlen); -} - -static inline ssize_t __sys_sendto(int sockfd, const void *buf, size_t len, - int flags, const struct sockaddr *dest_addr, - socklen_t addrlen) -{ - return (ssize_t) __do_syscall6(__NR_sendto, sockfd, buf, len, flags, - dest_addr, addrlen); -} - -static inline int __sys_close(int fd) -{ - return (int) __do_syscall1(__NR_close, fd); -} - -static inline ssize_t __sys_recv(int sockfd, void *buf, size_t len, int flags) -{ - return __sys_recvfrom(sockfd, buf, len, flags, NULL, NULL); -} - -static inline ssize_t __sys_send(int sockfd, const void *buf, size_t len, - int flags) -{ - return __sys_sendto(sockfd, buf, len, flags, NULL, 0); -} - -static inline int __sys_accept4(int sockfd, struct sockaddr *addr, - socklen_t *addrlen, int flags) -{ - return (int) __do_syscall4(__NR_accept4, sockfd, addr, addrlen, flags); -} - -static inline int __sys_epoll_ctl(int epfd, int op, int fd, - struct epoll_event *event) -{ - return (int) __do_syscall4(__NR_epoll_ctl, epfd, op, fd, event); -} - -static inline int __sys_setsockopt(int sockfd, int level, int optname, - const void *optval, socklen_t optlen) -{ - return (int) __do_syscall5(__NR_setsockopt, sockfd, level, optname, - optval, optlen); -} - -static inline int __sys_getsockopt(int sockfd, int level, int optname, - void *optval, socklen_t *optlen) -{ - return (int) __do_syscall5(__NR_getsockopt, sockfd, level, optname, - optval, optlen); -} - -static inline int __sys_socket(int domain, int type, int protocol) -{ - return (int) __do_syscall3(__NR_socket, domain, type, protocol); -} - -static inline int __sys_bind(int sockfd, const struct sockaddr *addr, - socklen_t addrlen) -{ - return (int) __do_syscall3(__NR_bind, sockfd, addr, addrlen); -} - -static inline int __sys_listen(int sockfd, int backlog) -{ - return (int) __do_syscall2(__NR_listen, sockfd, backlog); -} - -static inline int __sys_epoll_create1(int flags) -{ - return (int) __do_syscall1(__NR_epoll_create1, flags); -} - -static inline int __sys_connect(int sockfd, const struct sockaddr *addr, - socklen_t addrlen) -{ - return (int) __do_syscall3(__NR_connect, sockfd, addr, addrlen); -} - -static inline int __sys_getsockname(int sockfd, struct sockaddr *addr, - socklen_t *addrlen) -{ - return (int) __do_syscall3(__NR_getsockname, sockfd, addr, addrlen); -} - -static inline int __sys_timerfd_create(int clockid, int flags) -{ - return (int) __do_syscall2(__NR_timerfd_create, clockid, flags); -} - -static inline int __sys_timerfd_settime(int fd, int flags, - const struct itimerspec *new_value, - struct itimerspec *old_value) -{ - return (int) __do_syscall4(__NR_timerfd_settime, fd, flags, new_value, - old_value); -} - -#ifndef __NR_eventfd2 -#error "eventfd2 syscall not defined" -#endif - -static inline int __sys_eventfd(unsigned int c, int flags) -{ - return (int) __do_syscall2(__NR_eventfd2, c, flags); -} - -#else /* #ifdef __x86_64__ */ - -#include -static inline int __sys_epoll_wait(int epfd, struct epoll_event *events, - int maxevents, int timeout) -{ - int r = epoll_wait(epfd, events, maxevents, timeout); - return (r < 0) ? -errno : r; -} - -static inline ssize_t __sys_read(int fd, void *buf, size_t len) -{ - ssize_t r = read(fd, buf, len); - return (r < 0) ? -errno : r; -} - -static inline ssize_t __sys_write(int fd, const void *buf, size_t len) -{ - ssize_t r = write(fd, buf, len); - return (r < 0) ? -errno : r; -} - -static inline ssize_t __sys_recvfrom(int sockfd, void *buf, size_t len, - int flags, struct sockaddr *src_addr, - socklen_t *addrlen) -{ - ssize_t r = recvfrom(sockfd, buf, len, flags, src_addr, addrlen); - return (r < 0) ? -errno : r; -} - -static inline ssize_t __sys_sendto(int sockfd, const void *buf, size_t len, - int flags, const struct sockaddr *dest_addr, - socklen_t addrlen) -{ - ssize_t r = sendto(sockfd, buf, len, flags, dest_addr, addrlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_close(int fd) -{ - int r = close(fd); - return (r < 0) ? -errno : r; -} - -static inline ssize_t __sys_recv(int sockfd, void *buf, size_t len, int flags) -{ - return __sys_recvfrom(sockfd, buf, len, flags, NULL, NULL); -} - -static inline ssize_t __sys_send(int sockfd, const void *buf, size_t len, - int flags) -{ - return __sys_sendto(sockfd, buf, len, flags, NULL, 0); -} - -static inline int __sys_accept4(int sockfd, struct sockaddr *addr, - socklen_t *addrlen, int flags) -{ - int r = accept4(sockfd, addr, addrlen, flags); - return (r < 0) ? -errno : r; -} - -static inline int __sys_epoll_ctl(int epfd, int op, int fd, - struct epoll_event *event) -{ - int r = epoll_ctl(epfd, op, fd, event); - return (r < 0) ? -errno : r; -} - -static inline int __sys_setsockopt(int sockfd, int level, int optname, - const void *optval, socklen_t optlen) -{ - int r = setsockopt(sockfd, level, optname, optval, optlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_getsockopt(int sockfd, int level, int optname, - void *optval, socklen_t *optlen) -{ - int r = getsockopt(sockfd, level, optname, optval, optlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_socket(int domain, int type, int protocol) -{ - int r = socket(domain, type, protocol); - return (r < 0) ? -errno : r; -} - -static inline int __sys_bind(int sockfd, const struct sockaddr *addr, - socklen_t addrlen) -{ - int r = bind(sockfd, addr, addrlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_listen(int sockfd, int backlog) -{ - int r = listen(sockfd, backlog); - return (r < 0) ? -errno : r; -} - -static inline int __sys_epoll_create1(int flags) -{ - int r = epoll_create1(flags); - return (r < 0) ? -errno : r; -} - -static inline int __sys_eventfd(unsigned int c, int flags) -{ - int r = eventfd(c, flags); - return (r < 0) ? -errno : r; -} - -static inline int __sys_connect(int sockfd, const struct sockaddr *addr, - socklen_t addrlen) -{ - int r = connect(sockfd, addr, addrlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_getsockname(int sockfd, struct sockaddr *addr, - socklen_t *addrlen) -{ - int r = getsockname(sockfd, addr, addrlen); - return (r < 0) ? -errno : r; -} - -static inline int __sys_timerfd_create(int clockid, int flags) -{ - int r = timerfd_create(clockid, flags); - return (r < 0) ? -errno : r; -} - -static inline int __sys_timerfd_settime(int fd, int flags, - const struct itimerspec *new_value, - struct itimerspec *old_value) -{ - int r = timerfd_settime(fd, flags, new_value, old_value); - return (r < 0) ? -errno : r; -} -#endif /* #endif __x86_64__ */ - - __hot __attribute__((__format__(printf, 3, 4))) static void __pr_log(FILE *handle, int level, const char *fmt, ...) @@ -2789,11 +2392,10 @@ static int handle_socks5_data(struct gwp_wrk *w, struct gwp_conn_pair *gcp) out = gcp->target.buf + gcp->target.len; out_len = gcp->target.cap - gcp->target.len; r = gwp_socks5_conn_handle_data(sc, in, &in_len, out, &out_len); - if (r) - return (r == -EAGAIN) ? 0 : r; - gwp_conn_buf_advance(&gcp->client, in_len); gcp->target.len += out_len; + if (r) + return (r == -EAGAIN) ? 0 : r; if (sc->state == GWP_SOCKS5_ST_CMD_CONNECT) { r = socks5_prepare_target_addr(w, gcp); @@ -2839,12 +2441,15 @@ static int handle_ev_client_socks5(struct gwp_wrk *w, return sr; } - r = handle_socks5_data(w, gcp); - if (gcp->target.len) { - r = handle_socks5_pollout(w, gcp); - if (r && r != -EAGAIN) - return r; + if (gcp->conn_state == CONN_STATE_SOCKS5_DATA) { + r = handle_socks5_data(w, gcp); + if (gcp->target.len) { + r = handle_socks5_pollout(w, gcp); + if (r && r != -EAGAIN) + return r; + } } + return r; } diff --git a/src/gwproxy/socks5.c b/src/gwproxy/socks5.c index d682c40..812a1a9 100644 --- a/src/gwproxy/socks5.c +++ b/src/gwproxy/socks5.c @@ -890,6 +890,9 @@ int gwp_socks5_conn_handle_data(struct gwp_socks5_conn *conn, case GWP_SOCKS5_ST_CMD: r = handle_state_cmd(&arg); break; + case GWP_SOCKS5_ST_CMD_CONNECT: + r = 0; + goto out; default: return -EINVAL; } @@ -900,6 +903,7 @@ int gwp_socks5_conn_handle_data(struct gwp_socks5_conn *conn, if (r && r != -EAGAIN && r != -ENOBUFS) conn->state = GWP_SOCKS5_ST_ERR; +out: if (r == -ENOBUFS) { /* * If we run out of output buffer space, don't change diff --git a/src/gwproxy/syscall.h b/src/gwproxy/syscall.h new file mode 100644 index 0000000..4519a01 --- /dev/null +++ b/src/gwproxy/syscall.h @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Ammar Faizi + */ +#ifndef GWPROXY_SYSCALL_H +#define GWPROXY_SYSCALL_H + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Advantages of using inline assembly for syscalls: + * + * 1) Avoid the use of `errno`. Mostly it's implemented as + * a function call to `__errno_location()`. + * + * 2) Fewer register clobberings. x86-64 syscall only clobbers + * `rax`, `rcx`, `r11`, and `memory`. While libc function + * calls clobber `rax`, `rdi`, `rsi`, `rdx`, `r10`, `r8`, + * `r9`, `rcx`, `r11`, and `memory`. + */ +#ifdef __x86_64__ +#define __do_syscall0(NUM) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM) /* %rax */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall1(NUM, ARG1) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1) /* %rdi */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall2(NUM, ARG1, ARG2) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1), /* %rdi */ \ + "S"(ARG2) /* %rsi */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall3(NUM, ARG1, ARG2, ARG3) ({ \ + intptr_t rax; \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1), /* %rdi */ \ + "S"(ARG2), /* %rsi */ \ + "d"(ARG3) /* %rdx */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall4(NUM, ARG1, ARG2, ARG3, ARG4) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1), /* %rdi */ \ + "S"(ARG2), /* %rsi */ \ + "d"(ARG3), /* %rdx */ \ + "r"(__r10) /* %r10 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall5(NUM, ARG1, ARG2, ARG3, ARG4, ARG5) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1), /* %rdi */ \ + "S"(ARG2), /* %rsi */ \ + "d"(ARG3), /* %rdx */ \ + "r"(__r10), /* %r10 */ \ + "r"(__r8) /* %r8 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +#define __do_syscall6(NUM, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \ + intptr_t rax; \ + register __typeof__(ARG4) __r10 __asm__("r10") = (ARG4); \ + register __typeof__(ARG5) __r8 __asm__("r8") = (ARG5); \ + register __typeof__(ARG6) __r9 __asm__("r9") = (ARG6); \ + \ + __asm__ volatile( \ + "syscall" \ + : "=a"(rax) /* %rax */ \ + : "a"(NUM), /* %rax */ \ + "D"(ARG1), /* %rdi */ \ + "S"(ARG2), /* %rsi */ \ + "d"(ARG3), /* %rdx */ \ + "r"(__r10), /* %r10 */ \ + "r"(__r8), /* %r8 */ \ + "r"(__r9) /* %r9 */ \ + : "rcx", "r11", "memory" \ + ); \ + rax; \ +}) + +static inline int __sys_epoll_wait(int epfd, struct epoll_event *events, + int maxevents, int timeout) +{ + return (int) __do_syscall4(__NR_epoll_wait, epfd, events, maxevents, + timeout); +} + +static inline ssize_t __sys_read(int fd, void *buf, size_t len) +{ + return (ssize_t) __do_syscall3(__NR_read, fd, buf, len); +} + +static inline ssize_t __sys_write(int fd, const void *buf, size_t len) +{ + return (ssize_t) __do_syscall3(__NR_write, fd, buf, len); +} + +static inline ssize_t __sys_recvfrom(int sockfd, void *buf, size_t len, + int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + return (ssize_t) __do_syscall6(__NR_recvfrom, sockfd, buf, len, flags, + src_addr, addrlen); +} + +static inline ssize_t __sys_sendto(int sockfd, const void *buf, size_t len, + int flags, const struct sockaddr *dest_addr, + socklen_t addrlen) +{ + return (ssize_t) __do_syscall6(__NR_sendto, sockfd, buf, len, flags, + dest_addr, addrlen); +} + +static inline int __sys_close(int fd) +{ + return (int) __do_syscall1(__NR_close, fd); +} + +static inline ssize_t __sys_recv(int sockfd, void *buf, size_t len, int flags) +{ + return __sys_recvfrom(sockfd, buf, len, flags, NULL, NULL); +} + +static inline ssize_t __sys_send(int sockfd, const void *buf, size_t len, + int flags) +{ + return __sys_sendto(sockfd, buf, len, flags, NULL, 0); +} + +static inline int __sys_accept4(int sockfd, struct sockaddr *addr, + socklen_t *addrlen, int flags) +{ + return (int) __do_syscall4(__NR_accept4, sockfd, addr, addrlen, flags); +} + +static inline int __sys_epoll_ctl(int epfd, int op, int fd, + struct epoll_event *event) +{ + return (int) __do_syscall4(__NR_epoll_ctl, epfd, op, fd, event); +} + +static inline int __sys_setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen) +{ + return (int) __do_syscall5(__NR_setsockopt, sockfd, level, optname, + optval, optlen); +} + +static inline int __sys_getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen) +{ + return (int) __do_syscall5(__NR_getsockopt, sockfd, level, optname, + optval, optlen); +} + +static inline int __sys_socket(int domain, int type, int protocol) +{ + return (int) __do_syscall3(__NR_socket, domain, type, protocol); +} + +static inline int __sys_bind(int sockfd, const struct sockaddr *addr, + socklen_t addrlen) +{ + return (int) __do_syscall3(__NR_bind, sockfd, addr, addrlen); +} + +static inline int __sys_listen(int sockfd, int backlog) +{ + return (int) __do_syscall2(__NR_listen, sockfd, backlog); +} + +static inline int __sys_epoll_create1(int flags) +{ + return (int) __do_syscall1(__NR_epoll_create1, flags); +} + +static inline int __sys_connect(int sockfd, const struct sockaddr *addr, + socklen_t addrlen) +{ + return (int) __do_syscall3(__NR_connect, sockfd, addr, addrlen); +} + +static inline int __sys_getsockname(int sockfd, struct sockaddr *addr, + socklen_t *addrlen) +{ + return (int) __do_syscall3(__NR_getsockname, sockfd, addr, addrlen); +} + +static inline int __sys_timerfd_create(int clockid, int flags) +{ + return (int) __do_syscall2(__NR_timerfd_create, clockid, flags); +} + +static inline int __sys_timerfd_settime(int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + return (int) __do_syscall4(__NR_timerfd_settime, fd, flags, new_value, + old_value); +} + +#ifndef __NR_eventfd2 +#error "eventfd2 syscall not defined" +#endif + +static inline int __sys_eventfd(unsigned int c, int flags) +{ + return (int) __do_syscall2(__NR_eventfd2, c, flags); +} + +#else /* #ifdef __x86_64__ */ + +#include +static inline int __sys_epoll_wait(int epfd, struct epoll_event *events, + int maxevents, int timeout) +{ + int r = epoll_wait(epfd, events, maxevents, timeout); + return (r < 0) ? -errno : r; +} + +static inline ssize_t __sys_read(int fd, void *buf, size_t len) +{ + ssize_t r = read(fd, buf, len); + return (r < 0) ? -errno : r; +} + +static inline ssize_t __sys_write(int fd, const void *buf, size_t len) +{ + ssize_t r = write(fd, buf, len); + return (r < 0) ? -errno : r; +} + +static inline ssize_t __sys_recvfrom(int sockfd, void *buf, size_t len, + int flags, struct sockaddr *src_addr, + socklen_t *addrlen) +{ + ssize_t r = recvfrom(sockfd, buf, len, flags, src_addr, addrlen); + return (r < 0) ? -errno : r; +} + +static inline ssize_t __sys_sendto(int sockfd, const void *buf, size_t len, + int flags, const struct sockaddr *dest_addr, + socklen_t addrlen) +{ + ssize_t r = sendto(sockfd, buf, len, flags, dest_addr, addrlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_close(int fd) +{ + int r = close(fd); + return (r < 0) ? -errno : r; +} + +static inline ssize_t __sys_recv(int sockfd, void *buf, size_t len, int flags) +{ + return __sys_recvfrom(sockfd, buf, len, flags, NULL, NULL); +} + +static inline ssize_t __sys_send(int sockfd, const void *buf, size_t len, + int flags) +{ + return __sys_sendto(sockfd, buf, len, flags, NULL, 0); +} + +static inline int __sys_accept4(int sockfd, struct sockaddr *addr, + socklen_t *addrlen, int flags) +{ + int r = accept4(sockfd, addr, addrlen, flags); + return (r < 0) ? -errno : r; +} + +static inline int __sys_epoll_ctl(int epfd, int op, int fd, + struct epoll_event *event) +{ + int r = epoll_ctl(epfd, op, fd, event); + return (r < 0) ? -errno : r; +} + +static inline int __sys_setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen) +{ + int r = setsockopt(sockfd, level, optname, optval, optlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_getsockopt(int sockfd, int level, int optname, + void *optval, socklen_t *optlen) +{ + int r = getsockopt(sockfd, level, optname, optval, optlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_socket(int domain, int type, int protocol) +{ + int r = socket(domain, type, protocol); + return (r < 0) ? -errno : r; +} + +static inline int __sys_bind(int sockfd, const struct sockaddr *addr, + socklen_t addrlen) +{ + int r = bind(sockfd, addr, addrlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_listen(int sockfd, int backlog) +{ + int r = listen(sockfd, backlog); + return (r < 0) ? -errno : r; +} + +static inline int __sys_epoll_create1(int flags) +{ + int r = epoll_create1(flags); + return (r < 0) ? -errno : r; +} + +static inline int __sys_eventfd(unsigned int c, int flags) +{ + int r = eventfd(c, flags); + return (r < 0) ? -errno : r; +} + +static inline int __sys_connect(int sockfd, const struct sockaddr *addr, + socklen_t addrlen) +{ + int r = connect(sockfd, addr, addrlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_getsockname(int sockfd, struct sockaddr *addr, + socklen_t *addrlen) +{ + int r = getsockname(sockfd, addr, addrlen); + return (r < 0) ? -errno : r; +} + +static inline int __sys_timerfd_create(int clockid, int flags) +{ + int r = timerfd_create(clockid, flags); + return (r < 0) ? -errno : r; +} + +static inline int __sys_timerfd_settime(int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + int r = timerfd_settime(fd, flags, new_value, old_value); + return (r < 0) ? -errno : r; +} +#endif /* #endif __x86_64__ */ + +#endif /* #ifndef GWPROXY_SYSCALL_H */ diff --git a/src/gwproxy/tests/dns_cache.c b/src/gwproxy/tests/dns_cache.c new file mode 100644 index 0000000..315f7cd --- /dev/null +++ b/src/gwproxy/tests/dns_cache.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Ammar Faizi + */ +#ifdef NDEBUG +#undef NDEBUG +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +static void test_dns_cache_init_free(void) +{ + struct gwp_dns_cache *cache; + int r; + + /* Test successful initialization */ + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + assert(cache != NULL); + + /* Test freeing */ + gwp_dns_cache_free(cache); + + /* Test freeing NULL pointer (should not crash) */ + gwp_dns_cache_free(NULL); + + /* Test with different bucket sizes */ + r = gwp_dns_cache_init(&cache, 1); + assert(r == 0); + assert(cache != NULL); + gwp_dns_cache_free(cache); + + r = gwp_dns_cache_init(&cache, 1024); + assert(r == 0); + assert(cache != NULL); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_init_free: passed\n"); +} + +static void test_dns_cache_basic_insert_lookup(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + uint8_t *i4_addrs; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create some test address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + assert(ai != NULL); + + /* Insert entry */ + expire_time = time(NULL) + 300; /* 5 minutes from now */ + r = gwp_dns_cache_insert(cache, "test.local", ai, expire_time); + assert(r == 0); + + /* Lookup the entry */ + r = gwp_dns_cache_getent(cache, "test.local", &entry); + assert(r == 0); + assert(entry != NULL); + + /* Verify entry contents */ + assert(entry->name_len == strlen("test.local") + 1); + assert(strcmp((char *)entry->block, "test.local") == 0); + assert(entry->nr_i4 >= 1); + + /* Check IPv4 addresses */ + i4_addrs = gwp_dns_cache_entget_i4(entry); + assert(i4_addrs != NULL); + + /* Put the entry back */ + gwp_dns_cache_putent(entry); + + /* Try to lookup non-existent entry */ + r = gwp_dns_cache_getent(cache, "nonexistent.local", &entry); + assert(r == -ENOENT); + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_basic_insert_lookup: passed\n"); +} + +static void test_dns_cache_ipv6_support(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + uint8_t *i6_addrs; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create IPv6 address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("::1", "80", &hints, &ai); + assert(r == 0); + assert(ai != NULL); + + /* Insert IPv6 entry */ + expire_time = time(NULL) + 300; + r = gwp_dns_cache_insert(cache, "ipv6test.local", ai, expire_time); + assert(r == 0); + + /* Lookup the entry */ + r = gwp_dns_cache_getent(cache, "ipv6test.local", &entry); + assert(r == 0); + assert(entry != NULL); + + /* Verify IPv6 entry */ + assert(entry->nr_i6 >= 1); + i6_addrs = gwp_dns_cache_entget_i6(entry); + assert(i6_addrs != NULL); + + gwp_dns_cache_putent(entry); + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_ipv6_support: passed\n"); +} + +static void test_dns_cache_mixed_ipv4_ipv6(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai_v4, *ai_v6, *ai_mixed; + struct addrinfo hints; + time_t expire_time; + uint8_t *i4_addrs; + uint8_t *i6_addrs; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create mixed IPv4 + IPv6 address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai_v4); + assert(r == 0); + + hints.ai_family = AF_INET6; + r = getaddrinfo("::1", "80", &hints, &ai_v6); + assert(r == 0); + + /* Chain them together */ + ai_mixed = ai_v4; + ai_v4->ai_next = ai_v6; + + /* Insert mixed entry */ + expire_time = time(NULL) + 300; + r = gwp_dns_cache_insert(cache, "mixed.local", ai_mixed, expire_time); + assert(r == 0); + + /* Lookup and verify */ + r = gwp_dns_cache_getent(cache, "mixed.local", &entry); + assert(r == 0); + assert(entry != NULL); + + /* Should have both IPv4 and IPv6 addresses */ + assert(entry->nr_i4 >= 1); + assert(entry->nr_i6 >= 1); + + i4_addrs = gwp_dns_cache_entget_i4(entry); + i6_addrs = gwp_dns_cache_entget_i6(entry); + assert(i4_addrs != NULL); + assert(i6_addrs != NULL); + + gwp_dns_cache_putent(entry); + + /* Clean up - unchain before freeing */ + ai_v4->ai_next = NULL; + freeaddrinfo(ai_v4); + freeaddrinfo(ai_v6); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_mixed_ipv4_ipv6: passed\n"); +} + +static void test_dns_cache_entry_replacement(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry1, *entry2; + struct addrinfo *ai1, *ai2; + struct addrinfo hints; + time_t expire_time; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create first address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai1); + assert(r == 0); + + /* Create second address info */ + r = getaddrinfo("127.0.0.1", "443", &hints, &ai2); + assert(r == 0); + + /* Insert first entry */ + expire_time = time(NULL) + 300; + r = gwp_dns_cache_insert(cache, "replace.local", ai1, expire_time); + assert(r == 0); + + /* Get reference to first entry */ + r = gwp_dns_cache_getent(cache, "replace.local", &entry1); + assert(r == 0); + assert(entry1 != NULL); + + /* Insert second entry with same key (should replace) */ + r = gwp_dns_cache_insert(cache, "replace.local", ai2, expire_time + 100); + assert(r == 0); + + /* Get the new entry */ + r = gwp_dns_cache_getent(cache, "replace.local", &entry2); + assert(r == 0); + assert(entry2 != NULL); + + /* Entries should be different */ + assert(entry1 != entry2); + + /* Old entry should still be valid due to reference counting */ + gwp_dns_cache_putent(entry1); + gwp_dns_cache_putent(entry2); + + freeaddrinfo(ai1); + freeaddrinfo(ai2); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_entry_replacement: passed\n"); +} + +static void test_dns_cache_expiration(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + /* Insert entry that expires immediately */ + expire_time = time(NULL) - 1; /* Already expired */ + r = gwp_dns_cache_insert(cache, "expired.local", ai, expire_time); + assert(r == 0); + + /* Try to lookup expired entry */ + r = gwp_dns_cache_getent(cache, "expired.local", &entry); + assert(r == -ETIMEDOUT); + + /* Insert entry that expires in future */ + expire_time = time(NULL) + 300; + r = gwp_dns_cache_insert(cache, "future.local", ai, expire_time); + assert(r == 0); + + /* Should be able to lookup */ + r = gwp_dns_cache_getent(cache, "future.local", &entry); + assert(r == 0); + assert(entry != NULL); + gwp_dns_cache_putent(entry); + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_expiration: passed\n"); +} + +static void test_dns_cache_housekeeping(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t now; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + /* Insert multiple entries, some expired */ + now = time(NULL); + r = gwp_dns_cache_insert(cache, "expired1.local", ai, now - 10); + assert(r == 0); + r = gwp_dns_cache_insert(cache, "expired2.local", ai, now - 5); + assert(r == 0); + r = gwp_dns_cache_insert(cache, "valid1.local", ai, now + 300); + assert(r == 0); + r = gwp_dns_cache_insert(cache, "valid2.local", ai, now + 600); + assert(r == 0); + + /* Run housekeeping */ + gwp_dns_cache_housekeep(cache); + + /* Expired entries should be gone */ + r = gwp_dns_cache_getent(cache, "expired1.local", &entry); + assert(r == -ENOENT); + r = gwp_dns_cache_getent(cache, "expired2.local", &entry); + assert(r == -ENOENT); + + /* Valid entries should still be there */ + r = gwp_dns_cache_getent(cache, "valid1.local", &entry); + assert(r == 0); + gwp_dns_cache_putent(entry); + r = gwp_dns_cache_getent(cache, "valid2.local", &entry); + assert(r == 0); + gwp_dns_cache_putent(entry); + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_housekeeping: passed\n"); +} + +static void test_dns_cache_hash_collisions(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + int r, i; + char key[64]; + + /* Use small cache to force collisions */ + r = gwp_dns_cache_init(&cache, 4); + assert(r == 0); + + /* Create address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + expire_time = time(NULL) + 300; + + /* Insert many entries to force hash collisions */ + for (i = 0; i < 20; i++) { + snprintf(key, sizeof(key), "collision%d.local", i); + r = gwp_dns_cache_insert(cache, key, ai, expire_time); + assert(r == 0); + } + + /* Verify all entries can be found */ + for (i = 0; i < 20; i++) { + snprintf(key, sizeof(key), "collision%d.local", i); + r = gwp_dns_cache_getent(cache, key, &entry); + assert(r == 0); + assert(entry != NULL); + assert(strcmp((char *)entry->block, key) == 0); + gwp_dns_cache_putent(entry); + } + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_hash_collisions: passed\n"); +} + +static void test_dns_cache_reference_counting(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry1, *entry2, *entry3; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Create address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + /* Insert entry */ + expire_time = time(NULL) + 300; + r = gwp_dns_cache_insert(cache, "refcount.local", ai, expire_time); + assert(r == 0); + + /* Get multiple references to the same entry */ + r = gwp_dns_cache_getent(cache, "refcount.local", &entry1); + assert(r == 0); + assert(entry1 != NULL); + + r = gwp_dns_cache_getent(cache, "refcount.local", &entry2); + assert(r == 0); + assert(entry2 != NULL); + + r = gwp_dns_cache_getent(cache, "refcount.local", &entry3); + assert(r == 0); + assert(entry3 != NULL); + + /* All should point to the same entry */ + assert(entry1 == entry2); + assert(entry2 == entry3); + + /* Put references back */ + gwp_dns_cache_putent(entry1); + gwp_dns_cache_putent(entry2); + gwp_dns_cache_putent(entry3); + + /* Test putting NULL (should not crash) */ + gwp_dns_cache_putent(NULL); + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_reference_counting: passed\n"); +} + +static void test_dns_cache_invalid_inputs(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai, dummy_ai; + struct addrinfo hints; + time_t expire_time; + int r; + + r = gwp_dns_cache_init(&cache, 128); + assert(r == 0); + + /* Test invalid key lengths */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + expire_time = time(NULL) + 300; + + /* Empty key should fail */ + r = gwp_dns_cache_insert(cache, "", ai, expire_time); + assert(r == -EINVAL); + + /* Very long key should fail (>255 chars) */ + char long_key[300]; + memset(long_key, 'a', sizeof(long_key) - 1); + long_key[sizeof(long_key) - 1] = '\0'; + r = gwp_dns_cache_insert(cache, long_key, ai, expire_time); + assert(r == -EINVAL); + + /* NULL addrinfo should fail */ + r = gwp_dns_cache_insert(cache, "valid.local", NULL, expire_time); + assert(r == -EINVAL); + + /* Empty addrinfo (no addresses) should fail */ + memset(&dummy_ai, 0, sizeof(dummy_ai)); + dummy_ai.ai_family = AF_UNSPEC; /* Neither IPv4 nor IPv6 */ + r = gwp_dns_cache_insert(cache, "valid.local", &dummy_ai, expire_time); + assert(r == -EINVAL); + + /* Test lookup with invalid keys */ + r = gwp_dns_cache_getent(cache, "", &entry); + assert(r == -EINVAL); + + r = gwp_dns_cache_getent(cache, long_key, &entry); + assert(r == -EINVAL); + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_invalid_inputs: passed\n"); +} + +static void test_dns_cache_large_dataset(void) +{ + struct gwp_dns_cache *cache; + struct gwp_dns_cache_entry *entry; + struct addrinfo *ai; + struct addrinfo hints; + time_t expire_time; + int r, i; + char key[64]; + + r = gwp_dns_cache_init(&cache, 1024); + assert(r == 0); + + /* Create address info */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + r = getaddrinfo("127.0.0.1", "80", &hints, &ai); + assert(r == 0); + + expire_time = time(NULL) + 300; + + /* Insert many entries */ + for (i = 0; i < 1000; i++) { + snprintf(key, sizeof(key), "large%04d.local", i); + r = gwp_dns_cache_insert(cache, key, ai, expire_time); + assert(r == 0); + } + + /* Verify random entries can be found */ + for (i = 0; i < 100; i++) { + int idx = rand() % 1000; + snprintf(key, sizeof(key), "large%04d.local", idx); + r = gwp_dns_cache_getent(cache, key, &entry); + assert(r == 0); + assert(entry != NULL); + gwp_dns_cache_putent(entry); + } + + freeaddrinfo(ai); + gwp_dns_cache_free(cache); + + printf("test_dns_cache_large_dataset: passed\n"); +} + +int main(void) +{ + int i; + + printf("Running DNS cache tests 5000 times...\n"); + + for (i = 0; i < 5000; i++) { + if (i % 500 == 0) { + printf("Iteration %d/5000...\n", i + 1); + } + + test_dns_cache_init_free(); + test_dns_cache_basic_insert_lookup(); + test_dns_cache_ipv6_support(); + test_dns_cache_mixed_ipv4_ipv6(); + test_dns_cache_entry_replacement(); + test_dns_cache_expiration(); + test_dns_cache_housekeeping(); + test_dns_cache_hash_collisions(); + test_dns_cache_reference_counting(); + test_dns_cache_invalid_inputs(); + test_dns_cache_large_dataset(); + } + + printf("All DNS cache tests passed 5000 times!\n"); + return 0; +}