/* Minimal unit test runner: ./test_runner [test_name ...] */ #include #include #include #include #include "../src/config.h" #include "../src/json.h" #include "../src/net.h" #include "../src/proto.h" #include "../src/services.h" static int failures; #define CHECK(cond) do { \ if (!(cond)) { \ fprintf(stderr, " %s:%d: CHECK(%s) failed\n", __FILE__, __LINE__, #cond); \ failures++; \ } \ } while (0) /* Tests */ static void test_config(void) { struct pb_config c; config_defaults(&c); char l1[] = "Role = target"; char l2[] = " TargetPort=2200 # not a comment, atoi stops"; char l3[] = "# comment"; char l4[] = "IdentityFile = \" /etc/x \""; char l5[] = "SysopPassword = secret"; char l6[] = "garbage"; CHECK(config_parse_line(&c, l1) == 0 && c.role == ROLE_TARGET); CHECK(config_parse_line(&c, l2) == 0 && c.target_port == 2200); CHECK(config_parse_line(&c, l3) == 0); CHECK(config_parse_line(&c, l4) == 0 && !strcmp(c.identity_file, " /etc/x ")); CHECK(config_parse_line(&c, l5) == 0); CHECK(config_parse_line(&c, l6) == -1); } static void test_lines(void) { struct buf b; buf_init(&b); char out[PB_MAX_LINE]; buf_append(&b, "HELLO abc 7701\r\nSINK 1 tcp", 26); CHECK(line_next(&b, out, sizeof(out)) == 1 && !strcmp(out, "HELLO abc 7701")); CHECK(line_next(&b, out, sizeof(out)) == 0); buf_append(&b, " 0.0.0.0 80\n", 12); CHECK(line_next(&b, out, sizeof(out)) == 1); char *f[PB_MAX_FIELDS]; CHECK(line_split(out, f, PB_MAX_FIELDS) == 5 && !strcmp(f[4], "80")); buf_free(&b); char s1[16] = "a b\tc"; field_sanitise(s1); CHECK(!strcmp(s1, "a_b_c")); char s2[4] = ""; field_sanitise(s2); CHECK(!strcmp(s2, "-")); } static void test_udp_frames(void) { struct buf b; buf_init(&b); const char *p; CHECK(udp_frame_append(&b, "hello", 5) == 0); CHECK(udp_frame_append(&b, "", 0) == 0); CHECK(udp_frame_peek(&b, &p) == 5 && !memcmp(p, "hello", 5)); buf_consume(&b, 7); CHECK(udp_frame_peek(&b, &p) == -2); buf_consume(&b, 2); CHECK(udp_frame_peek(&b, &p) == 0); buf_append(&b, "\x00\x03" "ab", 4); CHECK(udp_frame_peek(&b, &p) == 0); // incomplete buf_free(&b); } static void test_services_parse(void) { struct svc_entry e; // 127.0.0.1:22 LISTEN, as printed on a little endian machine. const char *tcp = " 0: 0100007F:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 12345 1 0000000000000000 100 0 0 10 0"; CHECK(services_parse_line(tcp, 0, 0, &e) == 1); CHECK(e.port == 22 && e.inode == 12345 && e.proto == PROTO_TCP); // Established connection is skipped. const char *est = " 1: 0100007F:0016 0100007F:D431 01 00000000:00000000 00:00000000 00000000 0 0 222 1 0000000000000000 100 0 0 10 0"; CHECK(services_parse_line(est, 0, 0, &e) == 0); // :: port 53 UDP const char *udp6 = " 10: 00000000000000000000000000000000:0035 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 999 2 0000000000000000 0"; CHECK(services_parse_line(udp6, 1, 1, &e) == 1); CHECK(e.port == 53 && !strcmp(e.addr, "::") && e.proto == PROTO_UDP); CHECK(services_parse_line("bogus", 0, 0, &e) == -1); } static void test_json(void) { struct buf b; buf_init(&b); json_str(&b, "a\"b\\c\n\x01"); buf_append(&b, "", 1); CHECK(!strcmp(b.data, "\"a\\\"b\\\\c\\n\\u0001\"")); buf_free(&b); } static void test_token(void) { char a[PB_TOKEN_LEN + 1], b[PB_TOKEN_LEN + 1]; CHECK(random_token(a, sizeof(a)) == 0 && strlen(a) == PB_TOKEN_LEN); CHECK(random_token(b, sizeof(b)) == 0 && strcmp(a, b)); } static void test_sockaddr(void) { struct sockaddr_storage ss; char out[64]; CHECK(sockaddr_parse("1.2.3.4:5678", &ss) == 0 && ss.ss_family == AF_INET); sockaddr_str((struct sockaddr *)&ss, out, sizeof(out)); CHECK(!strcmp(out, "1.2.3.4:5678")); CHECK(sockaddr_parse("[2001:db8::1]:443", &ss) == 0 && ss.ss_family == AF_INET6); sockaddr_str((struct sockaddr *)&ss, out, sizeof(out)); CHECK(!strcmp(out, "[2001:db8::1]:443")); CHECK(sockaddr_parse("[::ffff:10.0.0.1]:80", &ss) == 0); sockaddr_unmap(&ss); sockaddr_str((struct sockaddr *)&ss, out, sizeof(out)); CHECK(ss.ss_family == AF_INET && !strcmp(out, "10.0.0.1:80")); CHECK(sockaddr_parse("1.2.3.4", &ss) == -1); CHECK(sockaddr_parse("1.2.3.4:99999", &ss) == -1); CHECK(sockaddr_parse("host:80", &ss) == -1); CHECK(sockaddr_parse("[::1:80", &ss) == -1); } static void test_proxy_v2(void) { static const unsigned char want4[] = { 0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, 0x54, 0x0a, 0x21, 0x11, 0x00, 0x0c, 1, 2, 3, 4, 10, 0, 0, 5, 0x16, 0x2e, 0x01, 0xbb, }; struct sockaddr_storage src, dst; unsigned char out[PROXY_V2_MAX]; sockaddr_parse("1.2.3.4:5678", &src); sockaddr_parse("10.0.0.5:443", &dst); size_t n = proxy_v2_header(out, PROTO_TCP, (struct sockaddr *)&src, (struct sockaddr *)&dst); CHECK(n == sizeof(want4) && !memcmp(out, want4, n)); // Mixed families become IPv6 with a v4-mapped address; UDP sets DGRAM. sockaddr_parse("[2001:db8::1]:1000", &dst); n = proxy_v2_header(out, PROTO_UDP, (struct sockaddr *)&src, (struct sockaddr *)&dst); static const unsigned char mapped[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4 }; CHECK(n == 52 && out[13] == 0x22 && out[15] == 36); CHECK(!memcmp(out + 16, mapped, 16) && out[32] == 0x20 && out[33] == 0x01); CHECK(out[48] == 0x16 && out[49] == 0x2e && out[50] == 0x03 && out[51] == 0xe8); } /* Runner */ static const struct { const char *name; void (*fn)(void); } tests[] = { { "config", test_config }, { "lines", test_lines }, { "udp_frames", test_udp_frames }, { "services_parse", test_services_parse }, { "json", test_json }, { "token", test_token }, { "sockaddr", test_sockaddr }, { "proxy_v2", test_proxy_v2 }, }; int main(int argc, char **argv) { int ran = 0; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { int want = argc < 2; for (int a = 1; a < argc; a++) want |= !strcmp(argv[a], tests[i].name); if (!want) continue; int before = failures; tests[i].fn(); printf("%s %s\n", failures == before ? "ok " : "FAIL", tests[i].name); ran++; } if (!ran) { fprintf(stderr, "no matching tests\n"); return 2; } return failures ? 1 : 0; }