/** BEGIN COPYRIGHT BLOCK * Copyright (C) 2025 Red Hat, Inc. * All rights reserved. * * License: GPL (version 3 or any later version). * See LICENSE for details. * END COPYRIGHT BLOCK **/ #include "../../test_slapd.h" #include #include typedef struct test_input { const char *input_str; int expected_result; size_t expected_len; int expected_proxy_connection; PRNetAddr expected_pr_netaddr_from; PRNetAddr expected_pr_netaddr_dest; } test_input; test_input test_cases[] = { { .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 12345 389\r\n", .expected_result = HAPROXY_HEADER_PARSED, .expected_len = 39, .expected_proxy_connection = 1, /* We need to support both big-endian (x390x) and little-endian (x86) architectures, * it's better to dynamically adjust the byte order in our test cases based on * the architecture of the system executing the tests.*/ #ifdef __s390x__ .expected_pr_netaddr_from = { .inet = { .family = PR_AF_INET, .ip = 0xC0A80001, .port = 0x3039 }}, .expected_pr_netaddr_dest = { .inet = { .family = PR_AF_INET, .ip = 0xC0A80002, .port = 0x0185 }} #else .expected_pr_netaddr_from = { .inet = { .family = PR_AF_INET, .ip = 0x0100A8C0, .port = 0x3930 }}, .expected_pr_netaddr_dest = { .inet = { .family = PR_AF_INET, .ip = 0x0200A8C0, .port = 0x8501 }} #endif }, { .input_str = "PROXY TCP6 2001:db8::1 2001:db8::2 12345 389\r\n", .expected_result = HAPROXY_HEADER_PARSED, .expected_len = 46, .expected_proxy_connection = 1, #ifdef __s390x__ .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}, .port = 0x3039 }}, .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}}, .port = 0x0185 }} #else .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}}}, .port = 0x3930 }}, .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}}, .port = 0x8501 }} #endif }, { .input_str = "PROXY TCP6 ::ffff:192.168.0.1 ::ffff:192.168.0.2 12345 389\r\n", .expected_result = HAPROXY_HEADER_PARSED, .expected_len = 54, .expected_proxy_connection = 1, #ifdef __s390x__ .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01}}}, .port = 0x3039 }}, .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x02}}}, .port = 0x0185 }} #else .expected_pr_netaddr_from = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x01}}}, .port = 0x3930 }}, .expected_pr_netaddr_dest = { .ipv6 = { .family = PR_AF_INET6, .ip = {{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0xa8, 0x00, 0x02}}}, .port = 0x8501 }} #endif }, /* Invalid IP */ { .input_str = "PROXY TCP4 256.168.0.1 192.168.0.2 12345 389\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, /* Invalid port */ { .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 123456 389\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, /* One port */ { .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2 12345\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, /* No ports */ { .input_str = "PROXY TCP4 192.168.0.1 192.168.0.2\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, /* Empty string */ { .input_str = "", .expected_result = HAPROXY_NOT_A_HEADER, .expected_proxy_connection = 0, }, /* Invalid protocol */ { .input_str = "PROXY TCP3 192.168.0.1 192.168.0.2 12345 389\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, /* Missing protocol */ { .input_str = "PROXY 192.168.0.1 192.168.0.2 12345 389\r\n", .expected_result = HAPROXY_ERROR, .expected_proxy_connection = 0, }, }; size_t num_tests = sizeof(test_cases) / sizeof(test_cases[0]); void test_libslapd_haproxy_v1(void **state) { (void)state; int result = 0; for (size_t i = 0; i < num_tests; i++) { int proxy_connection = 0; PRNetAddr pr_netaddr_from = {{0}}; PRNetAddr pr_netaddr_dest = {{0}}; size_t str_len = strlen(test_cases[i].input_str); result = haproxy_parse_v1_hdr(test_cases[i].input_str, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(result, test_cases[i].expected_result); assert_int_equal(proxy_connection, test_cases[i].expected_proxy_connection); if (test_cases[i].expected_result == 0) { // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Expected pr_netaddr_from: "); // slapi_log_prnetaddr(&test_cases[i].expected_pr_netaddr_from); // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Actual pr_netaddr_from: "); // slapi_log_prnetaddr(&pr_netaddr_from); // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Expected pr_netaddr_dest: "); // slapi_log_prnetaddr(&test_cases[i].expected_pr_netaddr_dest); // slapi_log_error(SLAPI_LOG_ERR, "haproxy_parse_v1_hdr", "Actual pr_netaddr_dest: "); // slapi_log_prnetaddr(&pr_netaddr_dest); assert_memory_equal(&test_cases[i].expected_pr_netaddr_from, &pr_netaddr_from, sizeof(PRNetAddr)); assert_memory_equal(&test_cases[i].expected_pr_netaddr_dest, &pr_netaddr_dest, sizeof(PRNetAddr)); } } } void test_libslapd_haproxy_v2_invalid(void **state) { (void) state; // Unused struct { char *desc; char *str; struct proxy_hdr_v2 hdr_v2; size_t str_len; int expected_result; int expected_proxy_connection; } tests[] = { {"short header", "short", {}, sizeof("short"), HAPROXY_NOT_A_HEADER, 0}, {"invalid header", "invalid_signature", {}, sizeof("invalid_signature"), HAPROXY_NOT_A_HEADER, 0}, {"invalid signature", NULL, {"INVALID", PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, PP2_HEADER_LEN + PP2_ADDR_LEN_INET, HAPROXY_NOT_A_HEADER, 0}, {"unsupported family", NULL, {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, 0x30 | PP2_TRANS_STREAM, htons(0)}, PP2_HEADER_LEN, HAPROXY_ERROR, 0}, {"unsupported protocol", NULL, {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | 0x30, htons(0)}, PP2_HEADER_LEN, HAPROXY_ERROR, 0}, {"invalid version", NULL, {PP2_SIGNATURE, (PP2_VERSION ^ 0xF0) | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, PP2_HEADER_LEN + PP2_ADDR_LEN_INET, HAPROXY_ERROR, 0}, {"valid header, wrong command", NULL, {PP2_SIGNATURE, PP2_VERSION | (PP2_VER_CMD_PROXY ^ 0xF0), PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET)}, PP2_HEADER_LEN + PP2_ADDR_LEN_INET, HAPROXY_ERROR, 0}, {"valid header, too long", NULL, {PP2_SIGNATURE, PP2_VERSION | PP2_VER_CMD_PROXY, PP2_FAM_INET | PP2_TRANS_STREAM, htons(PP2_ADDR_LEN_INET * 2)}, PP2_HEADER_LEN + PP2_ADDR_LEN_INET, HAPROXY_ERROR, 0} }; for (size_t i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { int proxy_connection; PRNetAddr pr_netaddr_from; PRNetAddr pr_netaddr_dest; char *str_to_test = tests[i].str ? tests[i].str : (char *) &tests[i].hdr_v2; int result = haproxy_parse_v2_hdr(str_to_test, &tests[i].str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(result, tests[i].expected_result); assert_int_equal(proxy_connection, tests[i].expected_proxy_connection); } } // Test for a valid proxy header v2 with unsupported transport protocol void test_libslapd_haproxy_v2_unsupported_protocol(void **state) { (void) state; // Unused // Create a sample string with valid proxy header v2 and unsupported transport protocol struct proxy_hdr_v2 hdr_v2; memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_PROXY; hdr_v2.fam = PP2_FAM_INET | 0x30; // 0x30 is unsupported hdr_v2.len = htons(0); size_t str_len = PP2_HEADER_LEN; int proxy_connection; PRNetAddr pr_netaddr_from; PRNetAddr pr_netaddr_dest; int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(result, HAPROXY_ERROR); assert_int_equal(proxy_connection, 0); } // Test for the case when the protocol version is invalid void test_libslapd_haproxy_v2_invalid_version(void **state) { (void) state; // Unused // Create a sample string with an invalid protocol version struct proxy_hdr_v2 hdr_v2; memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); hdr_v2.ver_cmd = (PP2_VERSION ^ 0xF0) | PP2_VER_CMD_PROXY; hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; hdr_v2.len = htons(PP2_ADDR_LEN_INET); size_t str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET; int proxy_connection; PRNetAddr pr_netaddr_from; PRNetAddr pr_netaddr_dest; int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(result, HAPROXY_ERROR); assert_int_equal(proxy_connection, 0); } // Test for the case when the protocol command is valid - IPv4 and IPv6 void test_libslapd_haproxy_v2_valid(void **state) { (void) state; // Unused // We need only first two test cases as they are valid size_t num_tests = 2; for (size_t i = 0; i < num_tests; i++) { struct proxy_hdr_v2 hdr_v2; memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_PROXY; size_t str_len; int proxy_connection; PRNetAddr pr_netaddr_from = {0}; PRNetAddr pr_netaddr_dest = {0}; if (i == 0) { // IPv4 test case hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; hdr_v2.len = htons(PP2_ADDR_LEN_INET); uint32_t src_addr = test_cases[i].expected_pr_netaddr_from.inet.ip; uint32_t dst_addr = test_cases[i].expected_pr_netaddr_dest.inet.ip; uint16_t src_port = test_cases[i].expected_pr_netaddr_from.inet.port; uint16_t dst_port = test_cases[i].expected_pr_netaddr_dest.inet.port; memcpy(&hdr_v2.addr.ip4.src_addr, &src_addr, sizeof(src_addr)); memcpy(&hdr_v2.addr.ip4.dst_addr, &dst_addr, sizeof(dst_addr)); memcpy(&hdr_v2.addr.ip4.src_port, &src_port, sizeof(src_port)); memcpy(&hdr_v2.addr.ip4.dst_port, &dst_port, sizeof(dst_port)); str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET; } else { // IPv6 test case hdr_v2.fam = PP2_FAM_INET6 | PP2_TRANS_STREAM; hdr_v2.len = htons(PP2_ADDR_LEN_INET6); uint8_t src_addr[16]; uint8_t dst_addr[16]; memcpy(src_addr, &test_cases[i].expected_pr_netaddr_from.ipv6.ip, sizeof(src_addr)); memcpy(dst_addr, &test_cases[i].expected_pr_netaddr_dest.ipv6.ip, sizeof(dst_addr)); uint16_t src_port = test_cases[i].expected_pr_netaddr_from.ipv6.port; uint16_t dst_port = test_cases[i].expected_pr_netaddr_dest.ipv6.port; memcpy(&hdr_v2.addr.ip6.src_addr, src_addr, sizeof(src_addr)); memcpy(&hdr_v2.addr.ip6.dst_addr, dst_addr, sizeof(dst_addr)); memcpy(&hdr_v2.addr.ip6.src_port, &src_port, sizeof(src_port)); memcpy(&hdr_v2.addr.ip6.dst_port, &dst_port, sizeof(dst_port)); str_len = PP2_HEADER_LEN + PP2_ADDR_LEN_INET6; } int rc = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(rc, HAPROXY_HEADER_PARSED); assert_int_equal(proxy_connection, 1); assert_memory_equal(&pr_netaddr_from, &test_cases[i].expected_pr_netaddr_from, sizeof(PRNetAddr)); assert_memory_equal(&pr_netaddr_dest, &test_cases[i].expected_pr_netaddr_dest, sizeof(PRNetAddr)); } } // Test for a valid proxy header v2 with LOCAL command void test_libslapd_haproxy_v2_valid_local(void **state) { (void) state; // Unused // Create a sample string with valid proxy header v2 and LOCAL command struct proxy_hdr_v2 hdr_v2; memcpy(hdr_v2.sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN); hdr_v2.ver_cmd = PP2_VERSION | PP2_VER_CMD_LOCAL; hdr_v2.fam = PP2_FAM_INET | PP2_TRANS_STREAM; hdr_v2.len = htons(0); size_t str_len = PP2_HEADER_LEN; int proxy_connection; PRNetAddr pr_netaddr_from; PRNetAddr pr_netaddr_dest; int result = haproxy_parse_v2_hdr((const char *) &hdr_v2, &str_len, &proxy_connection, &pr_netaddr_from, &pr_netaddr_dest); assert_int_equal(result, HAPROXY_HEADER_PARSED); assert_int_equal(proxy_connection, 0); } /* Test IPv4 subnet matching */ void test_haproxy_ipv4_subnet_matching(void **state) { PRNetAddr ip1, ip2, network; (void)state; /* Unused */ /* Test /24 subnet */ PR_StringToNetAddr("192.168.1.50", &ip1); PR_StringToNetAddr("192.168.1.0", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 24), 1); /* Test IP outside /24 subnet */ PR_StringToNetAddr("192.168.2.50", &ip2); assert_int_equal(haproxy_ipv4_in_subnet(ip2.inet.ip, network.inet.ip, 24), 0); /* Test /32 exact match */ PR_StringToNetAddr("192.168.1.0", &ip1); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 32), 1); /* Test /32 non-match */ PR_StringToNetAddr("192.168.1.1", &ip1); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 32), 0); /* Test /16 subnet */ PR_StringToNetAddr("192.168.0.0", &network); PR_StringToNetAddr("192.168.255.255", &ip1); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 16), 1); /* Test /0 matches everything */ PR_StringToNetAddr("1.2.3.4", &ip1); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 0), 1); } /* Test IPv6 subnet matching */ void test_haproxy_ipv6_subnet_matching(void **state) { PRNetAddr ip1, network; struct in6_addr *ip_v6, *net_v6; (void)state; /* Unused */ /* Test /64 subnet */ PR_StringToNetAddr("2001:db8::1234", &ip1); PR_StringToNetAddr("2001:db8::", &network); ip_v6 = (struct in6_addr *)&ip1.ipv6.ip; net_v6 = (struct in6_addr *)&network.ipv6.ip; assert_int_equal(haproxy_ipv6_in_subnet(ip_v6, net_v6, 64), 1); /* Test IP outside /64 subnet */ PR_StringToNetAddr("2001:db9::1234", &ip1); ip_v6 = (struct in6_addr *)&ip1.ipv6.ip; assert_int_equal(haproxy_ipv6_in_subnet(ip_v6, net_v6, 64), 0); /* Test /32 subnet */ PR_StringToNetAddr("2001:db8:abcd::1234", &ip1); PR_StringToNetAddr("2001:db8::", &network); ip_v6 = (struct in6_addr *)&ip1.ipv6.ip; net_v6 = (struct in6_addr *)&network.ipv6.ip; assert_int_equal(haproxy_ipv6_in_subnet(ip_v6, net_v6, 32), 1); /* Test /128 exact match */ PR_StringToNetAddr("2001:db8::1", &ip1); PR_StringToNetAddr("2001:db8::1", &network); ip_v6 = (struct in6_addr *)&ip1.ipv6.ip; net_v6 = (struct in6_addr *)&network.ipv6.ip; assert_int_equal(haproxy_ipv6_in_subnet(ip_v6, net_v6, 128), 1); /* Test /0 matches everything */ PR_StringToNetAddr("fe80::1", &ip1); ip_v6 = (struct in6_addr *)&ip1.ipv6.ip; assert_int_equal(haproxy_ipv6_in_subnet(ip_v6, net_v6, 0), 1); } /* Test trusted IP parsing and binary matching */ void test_haproxy_trusted_ip_parsing(void **state) { struct berval bv1 = {.bv_val = "192.168.1.0/24", .bv_len = 14}; struct berval bv2 = {.bv_val = "10.0.0.5", .bv_len = 8}; struct berval bv3 = {.bv_val = "2001:db8::/32", .bv_len = 13}; struct berval bv4 = {.bv_val = "fe80::1", .bv_len = 7}; struct berval *bvals[] = {&bv1, &bv2, &bv3, &bv4, NULL}; size_t count = 0; char errorbuf[1024]; haproxy_trusted_entry_t *parsed; PRNetAddr test_ip1, test_ip2, test_ip3, test_ip4, test_ip5, test_ip6, test_ip7; (void)state; /* Unused */ /* Create test configuration with mixed IPs and subnets */ /* Parse into binary format */ parsed = haproxy_parse_trusted_ips(bvals, &count, errorbuf); assert_non_null(parsed); assert_int_equal(count, 4); /* Verify parsed structure for IPv4 subnet */ assert_int_equal(parsed[0].is_subnet, 1); assert_int_equal(parsed[0].prefix_len, 24); assert_int_equal(parsed[0].network.raw.family, PR_AF_INET); /* Verify parsed structure for IPv4 single IP */ assert_int_equal(parsed[1].is_subnet, 0); assert_int_equal(parsed[1].prefix_len, -1); assert_int_equal(parsed[1].network.raw.family, PR_AF_INET); /* Verify parsed structure for IPv6 subnet */ assert_int_equal(parsed[2].is_subnet, 1); assert_int_equal(parsed[2].prefix_len, 32); assert_int_equal(parsed[2].network.raw.family, PR_AF_INET6); /* Verify parsed structure for IPv6 single IP */ assert_int_equal(parsed[3].is_subnet, 0); assert_int_equal(parsed[3].prefix_len, -1); assert_int_equal(parsed[3].network.raw.family, PR_AF_INET6); /* Test binary matching with parsed entries */ /* Test 1: IP in IPv4 subnet should match */ PR_StringToNetAddr("192.168.1.100", &test_ip1); assert_int_equal(haproxy_ip_matches_parsed(&test_ip1, parsed, count), 1); /* Test 2: IP outside IPv4 subnet should not match */ PR_StringToNetAddr("192.168.2.100", &test_ip2); assert_int_equal(haproxy_ip_matches_parsed(&test_ip2, parsed, count), 0); /* Test 3: Exact IPv4 match */ PR_StringToNetAddr("10.0.0.5", &test_ip3); assert_int_equal(haproxy_ip_matches_parsed(&test_ip3, parsed, count), 1); /* Test 4: IP in IPv6 subnet should match */ PR_StringToNetAddr("2001:db8::1234", &test_ip4); assert_int_equal(haproxy_ip_matches_parsed(&test_ip4, parsed, count), 1); /* Test 5: IP outside IPv6 subnet should not match */ PR_StringToNetAddr("2001:db9::1234", &test_ip5); assert_int_equal(haproxy_ip_matches_parsed(&test_ip5, parsed, count), 0); /* Test 6: Exact IPv6 match */ PR_StringToNetAddr("fe80::1", &test_ip6); assert_int_equal(haproxy_ip_matches_parsed(&test_ip6, parsed, count), 1); /* Test 7: IPv4-mapped IPv6 address normalization */ PR_StringToNetAddr("::ffff:192.168.1.50", &test_ip7); /* Should match the 192.168.1.0/24 subnet after normalization */ assert_int_equal(haproxy_ip_matches_parsed(&test_ip7, parsed, count), 1); /* Cleanup */ slapi_ch_free((void **)&parsed); } /* Test edge cases and error handling in parsing */ void test_haproxy_parsing_edge_cases(void **state) { size_t count = 0; char errorbuf[1024]; haproxy_trusted_entry_t *parsed; struct berval *empty[] = {NULL}; struct berval bv_8 = {.bv_val = "10.0.0.0/8", .bv_len = 10}; struct berval bv_16 = {.bv_val = "172.16.0.0/16", .bv_len = 13}; struct berval bv_32 = {.bv_val = "203.0.113.1/32", .bv_len = 14}; struct berval *prefixes[] = {&bv_8, &bv_16, &bv_32, NULL}; PRNetAddr test_ip; (void)state; /* Unused */ /* Test NULL input */ parsed = haproxy_parse_trusted_ips(NULL, &count, errorbuf); assert_null(parsed); assert_int_equal(count, 0); /* Test empty array */ parsed = haproxy_parse_trusted_ips(empty, &count, errorbuf); assert_null(parsed); assert_int_equal(count, 0); /* Test various CIDR prefix lengths */ parsed = haproxy_parse_trusted_ips(prefixes, &count, errorbuf); assert_non_null(parsed); assert_int_equal(count, 3); /* /8 should match wide range */ PR_StringToNetAddr("10.255.255.255", &test_ip); assert_int_equal(haproxy_ip_matches_parsed(&test_ip, parsed, count), 1); /* /16 should match within range */ PR_StringToNetAddr("172.16.99.99", &test_ip); assert_int_equal(haproxy_ip_matches_parsed(&test_ip, parsed, count), 1); /* Outside /16 */ PR_StringToNetAddr("172.17.0.1", &test_ip); assert_int_equal(haproxy_ip_matches_parsed(&test_ip, parsed, count), 0); /* /32 exact match only */ PR_StringToNetAddr("203.0.113.1", &test_ip); assert_int_equal(haproxy_ip_matches_parsed(&test_ip, parsed, count), 1); PR_StringToNetAddr("203.0.113.2", &test_ip); assert_int_equal(haproxy_ip_matches_parsed(&test_ip, parsed, count), 0); slapi_ch_free((void **)&parsed); } /* Test netmask precomputation during parsing */ void test_haproxy_netmask_precomputation(void **state) { struct berval bv1 = {.bv_val = "192.168.1.0/24", .bv_len = 14}; struct berval *bvals[] = {&bv1, NULL}; struct berval bv2 = {.bv_val = "2001:db8:abcd::/48", .bv_len = 18}; struct berval *bvals2[] = {&bv2, NULL}; struct berval bv3 = {.bv_val = "2001:db8::/33", .bv_len = 13}; struct berval *bvals3[] = {&bv3, NULL}; size_t count = 0; char errorbuf[1024]; haproxy_trusted_entry_t *parsed; uint32_t expected_mask; int i; (void)state; /* Unused */ /* IPv4 /24 netmask should be 0xFFFFFF00 (255.255.255.0) */ parsed = haproxy_parse_trusted_ips(bvals, &count, errorbuf); assert_non_null(parsed); assert_int_equal(count, 1); /* Verify netmask was pre-computed */ expected_mask = htonl(0xFFFFFF00); assert_int_equal(parsed[0].netmask.inet.ip, expected_mask); slapi_ch_free((void **)&parsed); /* IPv6 /48 netmask - first 6 bytes should be 0xFF, rest 0x00 */ parsed = haproxy_parse_trusted_ips(bvals2, &count, errorbuf); assert_non_null(parsed); assert_int_equal(count, 1); /* Verify IPv6 netmask bytes */ for (i = 0; i < 6; i++) { assert_int_equal(parsed[0].netmask.ipv6.ip.pr_s6_addr[i], 0xFF); } for (i = 6; i < 16; i++) { assert_int_equal(parsed[0].netmask.ipv6.ip.pr_s6_addr[i], 0x00); } slapi_ch_free((void **)&parsed); /* IPv6 /33 - partial byte mask (first 4 bytes + 1 bit) */ parsed = haproxy_parse_trusted_ips(bvals3, &count, errorbuf); assert_non_null(parsed); /* First 4 bytes should be 0xFF */ for (i = 0; i < 4; i++) { assert_int_equal(parsed[0].netmask.ipv6.ip.pr_s6_addr[i], 0xFF); } /* 5th byte should be 0x80 (10000000 binary - 1 bit set) */ assert_int_equal(parsed[0].netmask.ipv6.ip.pr_s6_addr[4], 0x80); /* Remaining bytes should be 0x00 */ for (i = 5; i < 16; i++) { assert_int_equal(parsed[0].netmask.ipv6.ip.pr_s6_addr[i], 0x00); } slapi_ch_free((void **)&parsed); } /* Test CIDR string matching */ void test_haproxy_ip_matches_cidr(void **state) { (void)state; /* Unused */ /* IPv4 subnet matching */ assert_int_equal(haproxy_ip_matches_cidr("192.168.1.50", "192.168.1.0/24"), 1); assert_int_equal(haproxy_ip_matches_cidr("192.168.2.50", "192.168.1.0/24"), 0); /* IPv4 exact match (no CIDR) */ assert_int_equal(haproxy_ip_matches_cidr("10.0.0.1", "10.0.0.1"), 1); assert_int_equal(haproxy_ip_matches_cidr("10.0.0.1", "10.0.0.2"), 0); /* IPv6 subnet matching */ assert_int_equal(haproxy_ip_matches_cidr("2001:db8::1234", "2001:db8::/32"), 1); assert_int_equal(haproxy_ip_matches_cidr("2001:db9::1234", "2001:db8::/32"), 0); /* IPv6 exact match */ assert_int_equal(haproxy_ip_matches_cidr("fe80::1", "fe80::1"), 1); assert_int_equal(haproxy_ip_matches_cidr("fe80::1", "fe80::2"), 0); /* Case insensitivity for IPv6 */ assert_int_equal(haproxy_ip_matches_cidr("2001:DB8::1", "2001:db8::1"), 1); } /* Test IPv4 mask calculation edge cases and undefined behavior prevention */ void test_haproxy_ipv4_mask_edge_cases(void **state) { PRNetAddr ip1, ip2, network; (void)state; /* Unused */ /* Test prefix_len = 0 (should match everything without undefined behavior) */ PR_StringToNetAddr("192.168.1.1", &ip1); PR_StringToNetAddr("10.0.0.0", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 0), 1); /* Test with completely different IPs - /0 should still match */ PR_StringToNetAddr("255.255.255.255", &ip1); PR_StringToNetAddr("0.0.0.0", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 0), 1); /* Test prefix_len = 32 (exact match only) */ PR_StringToNetAddr("192.168.1.1", &ip1); PR_StringToNetAddr("192.168.1.1", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 32), 1); /* Test prefix_len = 32 with different IPs */ PR_StringToNetAddr("192.168.1.2", &ip2); assert_int_equal(haproxy_ipv4_in_subnet(ip2.inet.ip, network.inet.ip, 32), 0); /* Test prefix_len = 1 (half of all IPs) */ PR_StringToNetAddr("127.255.255.255", &ip1); PR_StringToNetAddr("0.0.0.0", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 1), 1); PR_StringToNetAddr("128.0.0.0", &ip2); assert_int_equal(haproxy_ipv4_in_subnet(ip2.inet.ip, network.inet.ip, 1), 0); /* Test prefix_len = 31 (smallest non-trivial subnet) */ PR_StringToNetAddr("192.168.1.0", &network); PR_StringToNetAddr("192.168.1.0", &ip1); PR_StringToNetAddr("192.168.1.1", &ip2); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 31), 1); assert_int_equal(haproxy_ipv4_in_subnet(ip2.inet.ip, network.inet.ip, 31), 1); PR_StringToNetAddr("192.168.1.2", &ip1); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 31), 0); /* Test invalid prefix lengths */ PR_StringToNetAddr("192.168.1.1", &ip1); PR_StringToNetAddr("192.168.1.0", &network); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, -1), 0); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 33), 0); assert_int_equal(haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, 255), 0); /* Test various prefix lengths to ensure no undefined behavior */ PR_StringToNetAddr("10.0.0.0", &network); for (int prefix = 0; prefix <= 32; prefix++) { PR_StringToNetAddr("10.255.255.255", &ip1); /* Should not crash or exhibit undefined behavior */ int result = haproxy_ipv4_in_subnet(ip1.inet.ip, network.inet.ip, (int)prefix); /* For /8, 10.255.255.255 should match 10.0.0.0 */ if (prefix <= 8) { assert_int_equal(result, 1); } else if (prefix == 32) { assert_int_equal(result, 0); /* Not exact match */ } } }