// test_socks_client.c — SOCKS5-клиент: dial через mock-прокси (no-auth, auth, reject) #include "socks_client.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/socket_compat.h" #include "../lib/platform_compat.h" #include #include #include static int test_failed = 0; #define TASSERT(cond) do { \ if (!(cond)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, " FAIL: %s", #cond); test_failed = 1; return test_failed; } \ } while(0) #define MOCK_PORT 24771 enum { MOCK_NOAUTH = 0, MOCK_AUTH = 1, MOCK_REJECT = 2 }; struct mock { struct UASYNC *ua; socket_t listen_sock; void *listen_id; socket_t conn_sock; void *conn_id; uint8_t mode; uint8_t state; // 0=greeting, 1=auth, 2=request, 3=relay uint8_t buf[512]; size_t buf_len; int got_connect; }; static void mock_send(struct mock *m, const uint8_t *d, size_t n) { ssize_t s = send(m->conn_sock, d, n, 0); (void)s; } static void mock_process(struct mock *m) { for (;;) { if (m->state == 0) { // greeting if (m->buf_len < 3) return; uint8_t nm = m->buf[1]; if (m->buf_len < (size_t)(2 + nm)) return; if (m->mode == MOCK_AUTH) { uint8_t r[] = {0x05, 0x02}; mock_send(m, r, 2); m->state = 1; } else { uint8_t r[] = {0x05, 0x00}; mock_send(m, r, 2); m->state = 2; } size_t c = 2 + nm; memmove(m->buf, m->buf + c, m->buf_len - c); m->buf_len -= c; continue; } if (m->state == 1) { // auth (RFC1929) if (m->buf_len < 2) return; size_t ulen = m->buf[1]; if (m->buf_len < 2 + ulen + 1) return; size_t plen = m->buf[2 + ulen]; size_t total = 2 + ulen + 1 + plen; if (m->buf_len < total) return; uint8_t r[] = {0x01, 0x00}; mock_send(m, r, 2); m->state = 2; memmove(m->buf, m->buf + total, m->buf_len - total); m->buf_len -= total; continue; } if (m->state == 2) { // CONNECT request if (m->buf_len < 4) return; uint8_t atyp = m->buf[3]; size_t need = 4; if (atyp == 0x01) need += 6; else if (atyp == 0x03) { if (m->buf_len < 5) return; need += 1 + (size_t)m->buf[4] + 2; } else if (atyp == 0x04) need += 18; else return; if (m->buf_len < need) return; if (m->mode == MOCK_REJECT) { uint8_t r[] = {0x05, 0x05, 0x00, 0x01, 0,0,0,0, 0,0}; mock_send(m, r, 10); } else { uint8_t r[] = {0x05, 0x00, 0x00, 0x01, 0,0,0,0, 0,0}; mock_send(m, r, 10); m->got_connect = 1; } m->state = 3; memmove(m->buf, m->buf + need, m->buf_len - need); m->buf_len -= need; continue; } while (m->buf_len > 0) { // relay: echo mock_send(m, m->buf, 1); memmove(m->buf, m->buf + 1, m->buf_len - 1); m->buf_len -= 1; } return; } } static void mock_read_cb(socket_t sock, void *arg) { struct mock *m = (struct mock *)arg; (void)sock; uint8_t tmp[512]; ssize_t n = recv(m->conn_sock, tmp, sizeof(tmp), 0); if (n <= 0) return; memcpy(m->buf + m->buf_len, tmp, (size_t)n); m->buf_len += (size_t)n; mock_process(m); } static void mock_accept_cb(socket_t sock, void *arg) { struct mock *m = (struct mock *)arg; struct sockaddr_storage sa; socklen_t sl = sizeof(sa); m->conn_sock = accept(sock, (struct sockaddr *)&sa, &sl); if (m->conn_sock == SOCKET_INVALID) return; socket_set_nonblocking(m->conn_sock); m->conn_id = uasync_add_socket_t(m->ua, m->conn_sock, mock_read_cb, NULL, NULL, "mock_conn", m); } static int mock_start(struct mock *m, struct UASYNC *ua, int port, uint8_t mode) { memset(m, 0, sizeof(*m)); m->ua = ua; m->mode = mode; m->conn_sock = SOCKET_INVALID; m->listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (m->listen_sock == SOCKET_INVALID) return -1; socket_set_reuseaddr(m->listen_sock, 1); socket_set_nonblocking(m->listen_sock); struct sockaddr_in a; memset(&a, 0, sizeof(a)); a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); a.sin_port = htons((uint16_t)port); if (bind(m->listen_sock, (struct sockaddr *)&a, sizeof(a)) < 0) return -1; if (listen(m->listen_sock, 8) < 0) return -1; m->listen_id = uasync_add_socket_t(ua, m->listen_sock, mock_accept_cb, NULL, NULL, "mock_listen", m); return m->listen_id ? 0 : -1; } static void mock_stop(struct mock *m) { if (m->conn_id) uasync_remove_socket_t(m->ua, m->conn_sock); if (m->conn_sock != SOCKET_INVALID) socket_close_wrapper(m->conn_sock); if (m->listen_id) uasync_remove_socket_t(m->ua, m->listen_sock); socket_close_wrapper(m->listen_sock); } // ── Mock SOCKS5 UDP ASSOCIATE: TCP-control (greeting+associate) + UDP-релей с эхом ── struct udp_mock { struct UASYNC *ua; socket_t listen_sock; // TCP-control listener void *listen_id; socket_t ctrl_sock; // принятый control-коннект void *ctrl_id; uint8_t cbuf[512]; size_t cblen; uint8_t cstate; // 0=greeting, 1=associate socket_t udp_sock; // UDP-релей void *udp_id; uint16_t udp_port; int got_associate; int echo_count; uint8_t last_payload[256]; size_t last_payload_len; struct sockaddr_storage last_peer; }; static void umock_ctrl_process(struct udp_mock *m) { for (;;) { if (m->cstate == 0) { // greeting if (m->cblen < 3) return; uint8_t nm = m->cbuf[1]; if (m->cblen < (size_t)(2 + nm)) return; uint8_t r[] = {0x05, 0x00}; // no-auth send(m->ctrl_sock, r, 2, 0); m->cstate = 1; size_t c = 2 + nm; memmove(m->cbuf, m->cbuf + c, m->cblen - c); m->cblen -= c; continue; } if (m->cstate == 1) { // UDP ASSOCIATE request if (m->cblen < 4) return; uint8_t atyp = m->cbuf[3]; size_t need = 4; if (atyp == 0x01) need += 6; else if (atyp == 0x04) need += 18; else return; if (m->cblen < need) return; uint8_t r[10]; r[0] = 0x05; r[1] = 0x00; r[2] = 0x00; r[3] = 0x01; uint32_t ip = htonl(INADDR_LOOPBACK); memcpy(r + 4, &ip, 4); uint16_t port = htons(m->udp_port); memcpy(r + 8, &port, 2); send(m->ctrl_sock, r, 10, 0); m->got_associate++; m->cstate = 2; memmove(m->cbuf, m->cbuf + need, m->cblen - need); m->cblen -= need; continue; } m->cblen = 0; // idle return; } } static void umock_ctrl_read_cb(socket_t sock, void *arg) { struct udp_mock *m = (struct udp_mock *)arg; (void)sock; uint8_t tmp[512]; ssize_t n = recv(m->ctrl_sock, tmp, sizeof(tmp), 0); if (n <= 0) return; memcpy(m->cbuf + m->cblen, tmp, (size_t)n); m->cblen += (size_t)n; umock_ctrl_process(m); } static void umock_accept_cb(socket_t sock, void *arg) { struct udp_mock *m = (struct udp_mock *)arg; struct sockaddr_storage sa; socklen_t sl = sizeof(sa); socket_t cs = accept(sock, (struct sockaddr *)&sa, &sl); if (cs == SOCKET_INVALID) return; socket_set_nonblocking(cs); m->ctrl_sock = cs; m->cstate = 0; m->cblen = 0; m->ctrl_id = uasync_add_socket_t(m->ua, cs, umock_ctrl_read_cb, NULL, NULL, "umock_ctrl", m); } // Релей: принять обёрнутую датаграмму, сохранить dst (peer) + payload, отправить эхо с SRC=peer. static void umock_udp_read_cb(socket_t sock, void *arg) { struct udp_mock *m = (struct udp_mock *)arg; (void)sock; uint8_t raw[2048]; struct sockaddr_storage from; socklen_t fl = sizeof(from); ssize_t n = recvfrom(m->udp_sock, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); if (n < 10 || raw[0] || raw[1] || raw[2]) return; uint8_t atyp = raw[3]; size_t hlen; if (atyp == 0x01) hlen = 10; else if (atyp == 0x04) hlen = 22; else return; if ((size_t)n < hlen) return; memset(&m->last_peer, 0, sizeof(m->last_peer)); if (atyp == 0x01) { struct sockaddr_in *sa = (struct sockaddr_in *)&m->last_peer; sa->sin_family = AF_INET; memcpy(&sa->sin_addr.s_addr, raw + 4, 4); memcpy(&sa->sin_port, raw + 8, 2); } else { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&m->last_peer; sa6->sin6_family = AF_INET6; memcpy(&sa6->sin6_addr, raw + 4, 16); memcpy(&sa6->sin6_port, raw + 20, 2); } size_t plen = (size_t)n - hlen; memcpy(m->last_payload, raw + hlen, plen); m->last_payload_len = plen; m->echo_count++; uint8_t out[2048]; size_t o = 0; out[o++] = 0; out[o++] = 0; out[o++] = 0; if (m->last_peer.ss_family == AF_INET) { struct sockaddr_in *sa = (struct sockaddr_in *)&m->last_peer; out[o++] = 0x01; memcpy(out + o, &sa->sin_addr.s_addr, 4); o += 4; memcpy(out + o, &sa->sin_port, 2); o += 2; } else { struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&m->last_peer; out[o++] = 0x04; memcpy(out + o, &sa6->sin6_addr, 16); o += 16; memcpy(out + o, &sa6->sin6_port, 2); o += 2; } memcpy(out + o, m->last_payload, plen); o += plen; sendto(m->udp_sock, out, o, 0, (struct sockaddr *)&from, fl); } static int umock_start(struct udp_mock *m, struct UASYNC *ua, int listen_port) { memset(m, 0, sizeof(*m)); m->ua = ua; m->ctrl_sock = SOCKET_INVALID; m->listen_sock = socket(AF_INET, SOCK_STREAM, 0); if (m->listen_sock == SOCKET_INVALID) return -1; socket_set_reuseaddr(m->listen_sock, 1); socket_set_nonblocking(m->listen_sock); struct sockaddr_in a; memset(&a, 0, sizeof(a)); a.sin_family = AF_INET; a.sin_addr.s_addr = htonl(INADDR_LOOPBACK); a.sin_port = htons((uint16_t)listen_port); if (bind(m->listen_sock, (struct sockaddr *)&a, sizeof(a)) < 0) return -1; if (listen(m->listen_sock, 8) < 0) return -1; m->listen_id = uasync_add_socket_t(ua, m->listen_sock, umock_accept_cb, NULL, NULL, "umock_listen", m); m->udp_sock = socket(AF_INET, SOCK_DGRAM, 0); if (m->udp_sock == SOCKET_INVALID) return -1; socket_set_nonblocking(m->udp_sock); struct sockaddr_in ua_addr; memset(&ua_addr, 0, sizeof(ua_addr)); ua_addr.sin_family = AF_INET; ua_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (bind(m->udp_sock, (struct sockaddr *)&ua_addr, sizeof(ua_addr)) < 0) return -1; struct sockaddr_in got; socklen_t gl = sizeof(got); getsockname(m->udp_sock, (struct sockaddr *)&got, &gl); m->udp_port = ntohs(got.sin_port); m->udp_id = uasync_add_socket_t(ua, m->udp_sock, umock_udp_read_cb, NULL, NULL, "umock_udp", m); return (m->listen_id && m->udp_id) ? 0 : -1; } static void umock_stop(struct udp_mock *m) { if (m->ctrl_id) uasync_remove_socket_t(m->ua, m->ctrl_sock); if (m->ctrl_sock != SOCKET_INVALID) socket_close_wrapper(m->ctrl_sock); if (m->listen_id) uasync_remove_socket_t(m->ua, m->listen_sock); socket_close_wrapper(m->listen_sock); if (m->udp_id) uasync_remove_socket_t(m->ua, m->udp_sock); socket_close_wrapper(m->udp_sock); } static int g_su_ready = 0, g_su_err = 0; static void su_ready_cb(struct socks_udp *s, int err, void *arg) { (void)s; (void)arg; g_su_ready++; g_su_err = err; } static socket_t g_sock = SOCKET_INVALID; static int g_err = 0; static int g_done = 0; static void dial_cb(socket_t sock, int err, void *arg) { (void)arg; g_sock = sock; g_err = err; g_done = 1; } static void read_echo_cb(socket_t sock, void *arg) { int *got = (int *)arg; uint8_t b; if (recv(sock, &b, 1, 0) == 1) *got = b; } // Запустить dial, дождаться результата, вернуть 0 (успех) или err. static int run_dial(struct UASYNC *ua, struct socks_cfg *cfg, const char *target, uint16_t tport) { g_done = 0; g_sock = SOCKET_INVALID; g_err = 0; void *h = socks_dial(ua, cfg, target, tport, dial_cb, NULL, 0); if (!h) return -1; int ticks = 0; while (!g_done && ticks < 20000) { uasync_poll(ua, 10); ticks++; } if (!g_done) return -1; return g_err; } static int test1_noauth_v4(void) { struct UASYNC *ua = uasync_create(); TASSERT(ua); struct mock m; TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_NOAUTH) == 0); struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; TASSERT(run_dial(ua, &cfg, "127.0.0.1", 12345) == 0); TASSERT(g_sock != SOCKET_INVALID); TASSERT(m.got_connect == 1); int got = 0; void *sid = uasync_add_socket_t(ua, g_sock, read_echo_cb, NULL, NULL, "echo", &got); TASSERT(sid != NULL); uint8_t x = 'A'; send(g_sock, &x, 1, 0); int ticks = 0; while (got == 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; } TASSERT(got == 'A'); uasync_remove_socket_t(ua, g_sock); socket_close_wrapper(g_sock); mock_stop(&m); uasync_destroy(ua, 1); return 0; } static int test2_auth_domain(void) { struct UASYNC *ua = uasync_create(); TASSERT(ua); struct mock m; TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_AUTH) == 0); struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; strcpy(cfg.user, "alice"); strcpy(cfg.pass, "secret"); TASSERT(run_dial(ua, &cfg, "example.com", 443) == 0); TASSERT(g_sock != SOCKET_INVALID); TASSERT(m.got_connect == 1); socket_close_wrapper(g_sock); mock_stop(&m); uasync_destroy(ua, 1); return 0; } static int test3_reject(void) { struct UASYNC *ua = uasync_create(); TASSERT(ua); struct mock m; TASSERT(mock_start(&m, ua, MOCK_PORT, MOCK_REJECT) == 0); struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; int err = run_dial(ua, &cfg, "127.0.0.1", 12345); TASSERT(err != 0); TASSERT(g_sock == SOCKET_INVALID); mock_stop(&m); uasync_destroy(ua, 1); return 0; } static int test4_udp_associate(void) { struct UASYNC *ua = uasync_create(); TASSERT(ua); struct udp_mock m; TASSERT(umock_start(&m, ua, MOCK_PORT) == 0); socket_t lfd = socket(AF_INET, SOCK_DGRAM, 0); TASSERT(lfd != SOCKET_INVALID); socket_set_nonblocking(lfd); struct sockaddr_in lb; memset(&lb, 0, sizeof(lb)); lb.sin_family = AF_INET; lb.sin_addr.s_addr = htonl(INADDR_LOOPBACK); TASSERT(bind(lfd, (struct sockaddr *)&lb, sizeof(lb)) == 0); struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; g_su_ready = 0; g_su_err = 0; struct socks_udp *su = socks_udp_associate(ua, &cfg, lfd, su_ready_cb, NULL); TASSERT(su != NULL); int ticks = 0; while (g_su_ready == 0 && ticks < 20000) { uasync_poll(ua, 10); ticks++; } TASSERT(g_su_ready == 1); TASSERT(g_su_err == 0); TASSERT(m.got_associate == 1); struct sockaddr_storage peer; memset(&peer, 0, sizeof(peer)); struct sockaddr_in *p = (struct sockaddr_in *)&peer; p->sin_family = AF_INET; p->sin_addr.s_addr = htonl(INADDR_LOOPBACK); p->sin_port = htons(9999); ssize_t snd = socks_udp_sendto(su, (const uint8_t *)"hello", 5, &peer); TASSERT(snd > 0); uint8_t raw[2048]; struct sockaddr_storage from; socklen_t fl = sizeof(from); ssize_t got = -1; ticks = 0; while (ticks < 5000) { got = recvfrom(lfd, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); if (got > 0) break; uasync_poll(ua, 10); ticks++; } TASSERT(got > 0); uint8_t payload[256]; struct sockaddr_storage src; ssize_t plen = socks_udp_unwrap(su, raw, (size_t)got, payload, sizeof(payload), &src); TASSERT(plen == 5); TASSERT(memcmp(payload, "hello", 5) == 0); TASSERT(src.ss_family == AF_INET); TASSERT(((struct sockaddr_in *)&src)->sin_port == p->sin_port); socks_udp_destroy(su); socket_close_wrapper(lfd); umock_stop(&m); uasync_destroy(ua, 1); return 0; } static int test5_udp_reassociate(void) { struct UASYNC *ua = uasync_create(); TASSERT(ua); struct udp_mock m; TASSERT(umock_start(&m, ua, MOCK_PORT) == 0); socket_t lfd = socket(AF_INET, SOCK_DGRAM, 0); TASSERT(lfd != SOCKET_INVALID); socket_set_nonblocking(lfd); struct sockaddr_in lb; memset(&lb, 0, sizeof(lb)); lb.sin_family = AF_INET; lb.sin_addr.s_addr = htonl(INADDR_LOOPBACK); TASSERT(bind(lfd, (struct sockaddr *)&lb, sizeof(lb)) == 0); struct socks_cfg cfg; memset(&cfg, 0, sizeof(cfg)); strcpy(cfg.host, "127.0.0.1"); cfg.port = MOCK_PORT; g_su_ready = 0; g_su_err = 0; struct socks_udp *su = socks_udp_associate(ua, &cfg, lfd, su_ready_cb, NULL); TASSERT(su != NULL); int ticks = 0; while (g_su_ready == 0 && ticks < 20000) { uasync_poll(ua, 10); ticks++; } TASSERT(g_su_ready == 1); TASSERT(m.got_associate == 1); // Обрыв control-канала: закрываем серверную сторону → клиент должен пересоздать ассоциацию. if (m.ctrl_id) { uasync_remove_socket_t(m.ua, m.ctrl_sock); m.ctrl_id = NULL; } if (m.ctrl_sock != SOCKET_INVALID) { socket_close_wrapper(m.ctrl_sock); m.ctrl_sock = SOCKET_INVALID; } int ready_before = g_su_ready; ticks = 0; while (g_su_ready == ready_before && ticks < 50000) { uasync_poll(ua, 10); ticks++; } // backoff ~1с TASSERT(g_su_ready > ready_before); TASSERT(m.got_associate >= 2); // После re-associate канал снова работает. struct sockaddr_storage peer; memset(&peer, 0, sizeof(peer)); struct sockaddr_in *p = (struct sockaddr_in *)&peer; p->sin_family = AF_INET; p->sin_addr.s_addr = htonl(INADDR_LOOPBACK); p->sin_port = htons(7777); TASSERT(socks_udp_sendto(su, (const uint8_t *)"ping", 4, &peer) > 0); uint8_t raw[2048]; struct sockaddr_storage from; socklen_t fl = sizeof(from); ssize_t got = -1; ticks = 0; while (ticks < 5000) { got = recvfrom(lfd, raw, sizeof(raw), 0, (struct sockaddr *)&from, &fl); if (got > 0) break; uasync_poll(ua, 10); ticks++; } TASSERT(got > 0); uint8_t payload[256]; struct sockaddr_storage src; ssize_t plen = socks_udp_unwrap(su, raw, (size_t)got, payload, sizeof(payload), &src); TASSERT(plen == 4); TASSERT(memcmp(payload, "ping", 4) == 0); socks_udp_destroy(su); socket_close_wrapper(lfd); umock_stop(&m); uasync_destroy(ua, 1); return 0; } int main(void) { debug_set_level(DEBUG_LEVEL_ERROR); socket_platform_init(); struct { const char *name; int (*fn)(void); } tests[] = { {"test1_noauth_v4", test1_noauth_v4}, {"test2_auth_domain", test2_auth_domain}, {"test3_reject", test3_reject}, {"test4_udp_associate", test4_udp_associate}, {"test5_udp_reassociate", test5_udp_reassociate}, }; int n = (int)(sizeof(tests) / sizeof(tests[0])); int passed = 0; for (int i = 0; i < n; i++) { test_failed = 0; int r = tests[i].fn(); if (r == 0) { passed++; DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "PASS: %s", tests[i].name); } else { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL: %s", tests[i].name); } } socket_platform_cleanup(); DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", passed, n); return passed == n ? 0 : 1; }