diff --git a/tests/Makefile.am b/tests/Makefile.am index 8ea3a3ef..c7a531cf 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -266,6 +266,21 @@ else test_clock_ldflags = endif +if !OS_WINDOWS +check_PROGRAMS += test_socket_monitor +test_socket_monitor_SOURCES = test_socket_monitor.c +test_socket_monitor_LDADD = $(LIBUTUN) $(COMMON_LIBS) +test_socket_monitor_LDFLAGS = -Wl,--wrap=socket -Wl,--wrap=bind -Wl,--wrap=close -Wl,--wrap=socket_set_nonblocking \ + -Wl,--wrap=uasync_add_socket -Wl,--wrap=uasync_remove_socket -Wl,--wrap=u_calloc_impl \ + -Wl,--wrap=get_interface_ip_by_index -Wl,--wrap=get_interface_ipv6_addr_nl \ + -Wl,--wrap=get_default_route_netif_index -Wl,--wrap=get_outgoing_local_ip -Wl,--wrap=etcp_socket_cbk_fire +if OS_LINUX +test_socket_monitor_LDFLAGS += -Wl,--wrap=recvfrom +else +test_socket_monitor_LDFLAGS += -Wl,--wrap=read +endif +endif + # Вариант общего ядра с Android duty-cycle, без JNI и устройства. if OS_LINUX check_PROGRAMS += test_local_transport diff --git a/tests/test_socket_monitor.c b/tests/test_socket_monitor.c new file mode 100644 index 00000000..3bdaa3e8 --- /dev/null +++ b/tests/test_socket_monitor.c @@ -0,0 +1,211 @@ +/* Настоящий monitor с управляемыми native событиями, адресами ОС и отказами ресурсов. */ +#include "../src/utun_instance.h" +#include "../src/transport_layer/socket_monitor.h" +#include "../src/transport_layer/etcp_api.h" +#include "../src/transport_layer/etcp_connections.h" +#include "../lib/debug_config.h" +#include "../lib/mem.h" +#include +#include +#ifdef __linux__ +#include +#include +#else +#include +#endif + +#define MONITOR_FD 123456 +static struct UTUN_INSTANCE instance; +static struct UASYNC ua; +static struct utun_config config; +static socket_callback_t reader; +static void* reader_arg; +static int opened, registered, fail_step, remove_in_status, status_events, address_events; +static unsigned default_index = 3; +static uint32_t addresses[8]; +static int ipv6_available, outgoing_available = 1; +static uint8_t event[1024]; +static size_t event_length; +static int interrupt_read; + +extern void* __real_u_calloc_impl(uint32_t, uint32_t, const char*); +void* __wrap_u_calloc_impl(uint32_t count, uint32_t size, const char* where) { + if (fail_step == 1 && strstr(where, "socket_monitor.c")) return NULL; + return __real_u_calloc_impl(count, size, where); +} + +extern socket_t __real_socket(int, int, int); +socket_t __wrap_socket(int family, int type, int protocol) { +#ifdef __linux__ + int monitor = family == AF_NETLINK; +#else + int monitor = family == PF_ROUTE; +#endif + if (!monitor) return __real_socket(family, type, protocol); + if (fail_step == 2) { errno = EMFILE; return SOCKET_INVALID; } + assert(!opened); opened = 1; return MONITOR_FD; +} +extern int __real_bind(int, const struct sockaddr*, socklen_t); +int __wrap_bind(int fd, const struct sockaddr* address, socklen_t size) { + if (fd != MONITOR_FD) return __real_bind(fd, address, size); + if (fail_step == 3) { errno = EACCES; return -1; } + return 0; +} +extern int __real_close(int); +int __wrap_close(int fd) { + if (fd != MONITOR_FD) return __real_close(fd); + assert(opened); opened = 0; return 0; +} +extern int __real_socket_set_nonblocking(socket_t); +int __wrap_socket_set_nonblocking(socket_t fd) { + if (fd != MONITOR_FD) return __real_socket_set_nonblocking(fd); + if (fail_step == 4) { errno = EIO; return -1; } + return 0; +} +void* __wrap_uasync_add_socket(struct UASYNC* loop, int fd, socket_callback_t read_cb, + socket_callback_t write_cb, socket_callback_t except_cb, const char* name, void* arg) { + (void)name; assert(loop == &ua && fd == MONITOR_FD && opened && !registered && !write_cb && !except_cb); + if (fail_step == 5) return NULL; + registered = 1; reader = read_cb; reader_arg = arg; return ®istered; +} +err_t __wrap_uasync_remove_socket(struct UASYNC* loop, void* handle) { + assert(loop == &ua && handle == ®istered && registered); + registered = 0; reader = NULL; reader_arg = NULL; return ERR_OK; +} + +uint32_t __wrap_get_interface_ip_by_index(uint32_t index) { return index < 8 ? addresses[index] : 0; } +uint32_t __wrap_get_default_route_netif_index(int family) { assert(family == AF_INET6); return default_index; } +int __wrap_get_interface_ipv6_addr_nl(uint32_t index, int stable, uint8_t* out) { + (void)stable; + if (!ipv6_available) return -1; + memset(out, 0, 16); out[0] = 0xfe; out[1] = 0x80; out[15] = (uint8_t)index; return 0; +} +int __wrap_get_outgoing_local_ip(const struct sockaddr_storage* local, + const struct sockaddr_storage* remote, struct sockaddr_storage* out) { + assert(remote->ss_family == local->ss_family); + if (!outgoing_available) return -1; + *out = *local; + if (local->ss_family == AF_INET) ((struct sockaddr_in*)out)->sin_addr.s_addr = addresses[default_index]; + else { memset(&((struct sockaddr_in6*)out)->sin6_addr, 0, 16); ((struct sockaddr_in6*)out)->sin6_addr.s6_addr[15] = 9; } + return 0; +} + +static struct ETCP_SOCKET* add_socket(uint8_t id, uint64_t generation, uint32_t index) { + struct ETCP_SOCKET* es = u_calloc(1, sizeof(*es)); assert(es); + es->instance = &instance; es->sock_id = id; es->generation = generation; es->netif_index = index; + snprintf(es->name, sizeof(es->name), "test-%u", id); + struct sockaddr_in* local = (struct sockaddr_in*)&es->local_addr; + local->sin_family = AF_INET; local->sin_port = htons(1234); + es->next = instance.etcp_sockets; instance.etcp_sockets = es; return es; +} +static void clear_sockets(void) { + while (instance.etcp_sockets) { + struct ETCP_SOCKET* es = instance.etcp_sockets; + instance.etcp_sockets = es->next; u_free(es); + } +} +void __wrap_etcp_socket_cbk_fire(struct ETCP_SOCKET* es, int type) { + if (type == ETCP_SOCKET_EVENT_STATUS_CHANGED) status_events++; + else { assert(type == ETCP_SOCKET_EVENT_ADDR_CHANGED); address_events++; } + if (remove_in_status && type == ETCP_SOCKET_EVENT_STATUS_CHANGED) { + uint8_t id = es->sock_id; uint32_t index = es->netif_index; + remove_in_status = 0; clear_sockets(); add_socket(id, 999, index); + } +} + +#ifdef __linux__ +extern ssize_t __real_recvfrom(int, void*, size_t, int, struct sockaddr*, socklen_t*); +ssize_t __wrap_recvfrom(int fd, void* out, size_t cap, int flags, struct sockaddr* address, socklen_t* address_size) { + if (fd != MONITOR_FD) return __real_recvfrom(fd, out, cap, flags, address, address_size); + if (interrupt_read) { interrupt_read = 0; errno = EINTR; return -1; } + if (!event_length) { errno = EAGAIN; return -1; } + assert(cap >= event_length && *address_size >= sizeof(struct sockaddr_nl)); + memset(address, 0, sizeof(struct sockaddr_nl)); *address_size = sizeof(struct sockaddr_nl); + size_t len = event_length; memcpy(out, event, len); event_length = 0; return (ssize_t)len; +} +static void fire_event(int status, unsigned index) { + struct nlmsghdr* header = (struct nlmsghdr*)event; + memset(event, 0, sizeof(event)); + header->nlmsg_type = status ? RTM_NEWLINK : RTM_NEWADDR; + header->nlmsg_len = status ? NLMSG_LENGTH(sizeof(struct ifinfomsg)) : NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + if (status) { struct ifinfomsg* info = NLMSG_DATA(header); info->ifi_index = (int)index; info->ifi_flags = IFF_UP; } + else { struct ifaddrmsg* info = NLMSG_DATA(header); info->ifa_index = index; info->ifa_family = AF_INET; } + event_length = NLMSG_ALIGN(header->nlmsg_len); interrupt_read = 1; + reader(MONITOR_FD, reader_arg); assert(!event_length && !interrupt_read); +} +#else +extern ssize_t __real_read(int, void*, size_t); +ssize_t __wrap_read(int fd, void* out, size_t cap) { + if (fd != MONITOR_FD) return __real_read(fd, out, cap); + if (!event_length) { errno = EAGAIN; return -1; } + assert(cap >= event_length); + size_t len = event_length; memcpy(out, event, len); event_length = 0; return (ssize_t)len; +} +static void fire_event(int status, unsigned index) { + memset(event, 0, sizeof(event)); + if (status) { + struct if_msghdr header = {.ifm_msglen = sizeof(header), .ifm_version = RTM_VERSION, + .ifm_type = RTM_IFINFO, .ifm_index = index, .ifm_flags = IFF_UP}; + memcpy(event, &header, sizeof(header)); event_length = sizeof(header); + } else { + struct ifa_msghdr header = {.ifam_msglen = sizeof(header), .ifam_version = RTM_VERSION, + .ifam_type = RTM_NEWADDR, .ifam_index = index}; + memcpy(event, &header, sizeof(header)); event_length = sizeof(header); + } + reader(MONITOR_FD, reader_arg); assert(!event_length); +} +#endif + +static void test_addresses(void) { + struct ETCP_SOCKET* bound = add_socket(1, 1, 1); + addresses[1] = htonl(0xc0000201); socket_monitor_update_if_addr(bound); + assert(((struct sockaddr_in*)&bound->interface_addr)->sin_addr.s_addr == addresses[1]); + assert(((struct sockaddr_in*)&bound->interface_addr)->sin_port == htons(1234)); + addresses[1] = 0; socket_monitor_update_if_addr(bound); + assert(!((struct sockaddr_in*)&bound->interface_addr)->sin_addr.s_addr); + bound->local_addr.ss_family = AF_INET6; ipv6_available = 1; + socket_monitor_update_if_addr(bound); + assert(((struct sockaddr_in6*)&bound->interface_addr)->sin6_scope_id == 1); + ipv6_available = 0; socket_monitor_update_if_addr(bound); + assert(IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)&bound->interface_addr)->sin6_addr)); + bound->netif_index = 0; ipv6_available = 1; socket_monitor_update_if_addr(bound); + assert(((struct sockaddr_in6*)&bound->interface_addr)->sin6_scope_id == default_index); + default_index = 4; socket_monitor_update_if_addr(bound); + assert(((struct sockaddr_in6*)&bound->interface_addr)->sin6_scope_id == 4); + ipv6_available = outgoing_available = 0; socket_monitor_update_if_addr(bound); + assert(IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6*)&bound->interface_addr)->sin6_addr)); + clear_sockets(); outgoing_available = 1; + DEBUG_INFO(DEBUG_CATEGORY_SYS, "PASS monitor addresses: loss, port, IPv6 scope, default route replacement"); +} + +int main(void) { + debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); + size_t initial = u_get_allocated_count(); + instance.ua = &ua; instance.config = &config; + assert(socket_monitor_init(NULL) == -1); + instance.ua = NULL; assert(socket_monitor_init(&instance) == -1); instance.ua = &ua; + for (int step = 1; step <= 5; step++) { +#ifndef __linux__ + if (step == 3) continue; +#endif + fail_step = step; + assert(socket_monitor_init(&instance) == -1 && !instance.socket_monitor && !opened && !registered); + assert(u_get_allocated_count() == initial); + } + fail_step = 0; + config.global.auto_sockets = CFG_AUTO_SOCKETS_ANDROID; + assert(socket_monitor_init(&instance) == 0 && !instance.socket_monitor && !opened); + config.global.auto_sockets = 0; + assert(socket_monitor_init(&instance) == 0 && instance.socket_monitor && opened && registered); + assert(socket_monitor_init(&instance) == -1); + test_addresses(); + add_socket(2, 2, 1); add_socket(1, 1, 1); addresses[1] = htonl(0xc0000205); + fire_event(0, 1); assert(address_events == 2 && !status_events); + fire_event(0, 1); assert(address_events == 2); + remove_in_status = 1; fire_event(1, 1); + assert(status_events == 1 && address_events == 2 && instance.etcp_sockets->generation == 999); + clear_sockets(); socket_monitor_destroy(&instance); socket_monitor_destroy(&instance); socket_monitor_destroy(NULL); + assert(!instance.socket_monitor && !opened && !registered && u_get_allocated_count() == initial); + DEBUG_INFO(DEBUG_CATEGORY_SYS, "PASS monitor init faults, native address/link events, endpoint replacement and teardown"); + return 0; +}