bool test_allocate_ipv4_transport_address( void ) { struct tuple tuple; struct ipv4_tuple_address tuple_addr; struct in_addr expected_addr; bool success = true; success &= str_to_addr4_verbose(IPV4_ALLOCATED_ADDR, &expected_addr); success &= inject_bib_entry( IPPROTO_ICMP ); success &= inject_bib_entry( IPPROTO_TCP ); success &= inject_bib_entry( IPPROTO_UDP ); if (!success) return false; init_tuple_for_test_ipv6(&tuple, IPPROTO_ICMP); success &= assert_true(allocate_ipv4_transport_address(&tuple, IPPROTO_ICMP, &tuple_addr), "Function result for ICMP"); success &= assert_equals_ipv4(&expected_addr , &tuple_addr.address, "IPv4 address for ICMP"); init_tuple_for_test_ipv6(&tuple, IPPROTO_TCP); success &= assert_true(allocate_ipv4_transport_address(&tuple, IPPROTO_TCP, &tuple_addr), "Function result for TCP"); success &= assert_equals_ipv4(&expected_addr , &tuple_addr.address, "IPv4 address for TCP"); success &= assert_true(tuple_addr.l4_id > 1023, "Port range for TCP"); init_tuple_for_test_ipv6(&tuple, IPPROTO_UDP); success &= assert_true(allocate_ipv4_transport_address(&tuple, IPPROTO_UDP, &tuple_addr), "Function result for UDP"); success &= assert_equals_ipv4(&expected_addr , &tuple_addr.address, "IPv4 address for UDP"); success &= assert_true(tuple_addr.l4_id % 2 == 0, "Port parity for UDP"); success &= assert_true( tuple_addr.l4_id > 1023, "Port range for UDP"); return success; }
static bool test_6to4(l4_protocol l4_proto) { struct tuple incoming, outgoing; bool success = true; incoming.src.addr.ipv6 = remote_ipv6; incoming.dst.addr.ipv6 = local_ipv6; incoming.src.l4_id = 1500; /* Lookup will use this. */ incoming.dst.l4_id = 123; /* Whatever */ incoming.l3_proto = L3PROTO_IPV6; incoming.l4_proto = l4_proto; if (l4_proto != L4PROTO_ICMP) { success &= assert_equals_int(VER_CONTINUE, tuple5(&incoming, &outgoing), "Function5 call"); success &= assert_equals_u16(80, outgoing.src.l4_id, "Source port"); success &= assert_equals_u16(123, outgoing.dst.l4_id, "Destination port"); } else { success &= assert_equals_int(VER_CONTINUE, tuple3(&incoming, &outgoing), "Function3 call"); success &= assert_equals_u16(80, outgoing.icmp_id, "ICMP ID"); } success &= assert_equals_ipv4(&local_ipv4, &outgoing.src.addr.ipv4, "Source address"); success &= assert_equals_ipv4(&remote_ipv4, &outgoing.dst.addr.ipv4, "Destination address"); success &= assert_equals_u16(L3PROTO_IPV4, outgoing.l3_proto, "Layer-3 protocol"); success &= assert_equals_u8(l4_proto, outgoing.l4_proto, "Layer-4 protocol"); return success; }
bool validate_ipv4_hdr(struct iphdr *hdr, u16 total_len, u16 id, u16 df, u16 mf, u16 frag_off, u8 protocol, struct tuple *tuple4) { struct in_addr addr; bool success = true; success &= assert_equals_u8(4, hdr->version, "IPv4hdr-Version"); success &= assert_equals_u8(5, hdr->ihl, "IPv4hdr-IHL"); success &= assert_equals_u8(0, hdr->tos, "IPv4hdr-TOS"); success &= assert_equals_u16(total_len, be16_to_cpu(hdr->tot_len), "IPv4hdr-total length"); success &= assert_equals_u16(id, be16_to_cpu(hdr->id), "IPv4hdr-ID"); success &= assert_equals_u16(df, be16_to_cpu(hdr->frag_off) & IP_DF, "IPv4hdr-DF"); success &= assert_equals_u16(mf, be16_to_cpu(hdr->frag_off) & IP_MF, "IPv4hdr-MF"); success &= assert_equals_u16(frag_off, get_fragment_offset_ipv4(hdr), "IPv4hdr-Frag offset"); /* success &= assert_equals_u8(, hdr->ttl, "IPv4 header - TTL"); */ success &= assert_equals_u8(protocol, hdr->protocol, "IPv4hdr-protocol"); addr.s_addr = hdr->saddr; success &= assert_equals_ipv4(&tuple4->src.addr4.l3, &addr, "IPv4hdr-src address"); addr.s_addr = hdr->daddr; success &= assert_equals_ipv4(&tuple4->dst.addr4.l3, &addr, "IPv4hdr-dst address"); return success; }
static bool test_get_any_addr_aux(l4_protocol proto, int min_range, int max_range, int range_step, int range_outside) { struct ipv4_transport_addr tuple_addr; int p; bool success = true; for (p = min_range; p <= max_range; p += range_step) { success &= assert_equals_int(0, pool4_get_any_addr(proto, p, &tuple_addr), "Matched borrow 1-result"); success &= assert_equals_ipv4(&expected_ips[0], &tuple_addr.l3, "Matched borrow 1-address"); success &= assert_false(ports[0][tuple_addr.l4], "Matched borrow 1-port"); ports[0][tuple_addr.l4] = true; success &= assert_equals_int(0, pool4_get_any_addr(proto, p, &tuple_addr), "Matched borrow 2-result"); success &= assert_equals_ipv4(&expected_ips[1], &tuple_addr.l3, "Matched borrow 2-address"); success &= assert_false(ports[1][tuple_addr.l4], "Matched borrow 2-port"); ports[1][tuple_addr.l4] = true; if (!success) return success; } /* At this point, the pool should not have low even ports, so it should lend random data. */ for (p = 0; p < range_outside; p += 1) { success &= assert_equals_int(0, pool4_get_any_addr(proto, 10, &tuple_addr), "Mismatched borrow 1-result"); success &= assert_equals_ipv4(&expected_ips[0], &tuple_addr.l3, "Mismatched borrow 1-address"); success &= assert_false(ports[0][tuple_addr.l4], "Mismatched borrow 1-port"); ports[0][tuple_addr.l4] = true; success &= assert_equals_int(0, pool4_get_any_addr(proto, 10, &tuple_addr), "Mismatched borrow 2-result"); success &= assert_equals_ipv4(&expected_ips[1], &tuple_addr.l3, "Mismatched borrow 2-address"); success &= assert_false(ports[1][tuple_addr.l4], "Mismatched borrow 2-port"); ports[1][tuple_addr.l4] = true; if (!success) return success; } /* The pool ran out of ports. */ success &= assert_equals_int(-ESRCH, pool4_get_any_addr(proto, 10, &tuple_addr), "Exhausted pool"); return success; }
static bool test_allocate_aux(struct tuple *tuple6, struct in_addr *same_addr, struct in_addr *out_addr, bool test_port) { struct ipv4_transport_addr result; bool success = true; struct host6_node *host6; if (host6_node_get_or_create(&tuple6->src.addr6.l3, &host6)) return false; success &= assert_equals_int(0, allocate_transport_address(host6, tuple6, &result), "function result"); /* BTW: Because in_addrs are __be32s, "1.1.1.1" is the same as "0x1010101U" */ success &= assert_true(result.l3.s_addr == htonl(0x1010101U) || result.l3.s_addr == htonl(0x2020202U), "Result address is in pool"); if (same_addr) success &= assert_equals_ipv4(same_addr, &result.l3, "Result address was recycled"); if (test_port) { success &= assert_true(is_same_range(tuple6->src.addr6.l4, result.l4), "Result port is in the requested range"); success &= assert_true(is_same_parity(tuple6->src.addr6.l4, result.l4), "Result port has the same parity as requested"); } success &= bib_inject(&tuple6->src.addr6.l3, tuple6->src.addr6.l4, &result.l3, result.l4, L4PROTO_UDP) != NULL; if (out_addr) *out_addr = result.l3; host6_node_return(host6); return success; }
static bool assert_tuple_addr(struct in_addr *expected_address, __u16 expected_port, struct ipv4_tuple_address *actual, char *test_name) { bool success = true; success &= assert_equals_ipv4(expected_address, &actual->address, test_name); success &= assert_equals_u16(expected_port, actual->l4_id, test_name); return success; }
bool test_addr_6to4(struct in6_addr *src, struct ipv6_prefix *prefix, struct in_addr *expected) { struct in_addr actual; bool success = true; success &= assert_true(addr_6to4(src, prefix, &actual), "Extract IPv4-result"); success &= assert_equals_ipv4(expected, &actual, "Extract IPv4-out"); return success; }
static bool test_6to4(bool (*function)(struct tuple *, struct tuple *), u_int8_t in_l4_protocol, u_int8_t out_l4_protocol) { struct tuple incoming, outgoing; bool success = true; incoming.src.addr.ipv6 = remote_ipv6; incoming.dst.addr.ipv6 = local_ipv6; incoming.src.l4_id = 1500; // Lookup will use this. incoming.dst.l4_id = 123; // Whatever incoming.l3_proto = PF_INET6; incoming.l4_proto = in_l4_protocol; success &= assert_true(function(&incoming, &outgoing), "Function call"); success &= assert_equals_ipv4(&local_ipv4, &outgoing.src.addr.ipv4, "Source address"); success &= assert_equals_ipv4(&remote_ipv4, &outgoing.dst.addr.ipv4, "Destination address"); success &= assert_equals_u16(PF_INET, outgoing.l3_proto, "Layer-3 protocol"); success &= assert_equals_u8(out_l4_protocol, outgoing.l4_proto, "Layer-4 protocol"); // TODO (test) need to test ports? return success; }
bool test_transport_address_ipv4( void ) { struct in_addr addr; struct ipv4_tuple_address ta; bool success = true; if (!str_to_addr4_verbose(IPV4_TRANSPORT_ADDR, &addr)) return false; transport_address_ipv4( addr, IPV4_TRANSPORT_PORT, &ta ); success &= assert_equals_ipv4(&ta.address, &addr, "Check that the address part of an IPv4 transport address is correct."); success &= assert_equals_u16(ta.l4_id, IPV4_TRANSPORT_PORT, "Check that the port part of an IPv4 transport address is correct."); return success; }
static bool test_get_any_aux(u_int8_t l4protocol, u32 port_min, u32 port_max, u32 step, char *test_name) { u32 addr_ctr, port_ctr; struct ipv4_tuple_address result; bool success = true; for (addr_ctr = 0; addr_ctr < ARRAY_SIZE(expected_ips); addr_ctr++) { for (port_ctr = port_min; port_ctr <= port_max; port_ctr += step) { success &= assert_true(pool4_get_any(l4protocol, port_ctr, &result), test_name); success &= assert_equals_ipv4(&expected_ips[addr_ctr], &result.address, test_name); success &= assert_false(ports[addr_ctr][result.l4_id], test_name); ports[addr_ctr][result.l4_id] = true; } } success &= assert_false(pool4_get_any(l4protocol, 0, &result), test_name); return success; }
bool test_extract_ipv4_from_ipv6( void ) { struct in6_addr addr6; struct in_addr extracted4; struct in_addr correct4; bool success = true; if (pool6_init(NULL, 0) != 0) return false; success &= str_to_addr6_verbose(IPV6_EXTRACT_ADDR, &addr6); success &= str_to_addr4_verbose(IPV4_EXTRACTED_ADDR, &correct4); success &= assert_true(extract_ipv4(&addr6, &extracted4), "Check that an IPv4 address can be extracted from an IPv6 address."); success &= assert_equals_ipv4(&extracted4, &correct4, "Assert that the extraction of the IPv4 address was correct."); pool6_destroy(); return success; }
bool test_allocate_ipv4_transport_address( void ) { u_int8_t protocols[] = { IPPROTO_ICMP, IPPROTO_TCP, IPPROTO_UDP }; __u16 expected_ports[] = { IPV6_ALLOCATE_PORT, IPV6_ALLOCATE_PORT, IPV6_ALLOCATE_PORT }; struct in_addr expected_addr; struct tuple tuple; struct ipv4_tuple_address new_ipv4_transport_address; bool success = true; int i; success &= pool6_init(); success &= pool4_init(true); success &= bib_init(); success &= str_to_addr4_verbose(IPV4_ALLOCATED_ADDR, &expected_addr); success &= inject_bib_entry( IPPROTO_ICMP ); success &= inject_bib_entry( IPPROTO_TCP ); success &= inject_bib_entry( IPPROTO_UDP ); if (!success) return false; for (i = 0; i < ARRAY_SIZE(protocols); i++) { init_tuple_for_test_ipv6(&tuple, protocols[i]); success &= assert_true(allocate_ipv4_transport_address(&tuple, protocols[i], &new_ipv4_transport_address), "Check that we can allocate a brand new IPv4 transport address."); success &= assert_equals_ipv4(&expected_addr , &new_ipv4_transport_address.address, "Check that the allocated IPv4 address is correct."); success &= assert_equals_u16( expected_ports[i], new_ipv4_transport_address.l4_id, "Check that the allocated IPv4 port is correct."); } bib_destroy(); pool4_destroy(); pool6_destroy(); return success; }
/** * Only UDP and its lower even range of ports is tested here. */ static bool test_return_function(void) { struct ipv4_tuple_address query, result; bool success = true; int addr_ctr, port_ctr; /* Try to return the entire pool, even though we haven't borrowed anything. */ for (addr_ctr = 0; addr_ctr < ARRAY_SIZE(expected_ips); addr_ctr++) { result.address = expected_ips[addr_ctr]; for (port_ctr = 0; port_ctr < 1024; port_ctr += 2) { result.l4_id = port_ctr; success &= assert_false(pool4_return(IPPROTO_UDP, &result), ""); } } /* Borrow the entire pool. */ for (addr_ctr = 0; addr_ctr < ARRAY_SIZE(expected_ips); addr_ctr++) { for (port_ctr = 0; port_ctr < 1024; port_ctr += 2) { success &= assert_true(pool4_get_any(IPPROTO_UDP, port_ctr, &result), "Borrow-result"); success &= assert_equals_ipv4(&expected_ips[addr_ctr], &result.address, "Borrow-addr"); success &= assert_false(ports[addr_ctr][result.l4_id], "Borrow-port"); ports[addr_ctr][result.l4_id] = true; } } success &= assert_false(pool4_get_any(IPPROTO_UDP, 0, &result), "Pool should be exhausted."); if (!success) return success; /* Return something from the first address. */ result.address = expected_ips[0]; result.l4_id = 1000; success &= assert_true(pool4_return(IPPROTO_UDP, &result), "Return"); ports[0][result.l4_id] = false; if (!success) return success; /* Re-borrow it, assert it's the same one. */ success &= assert_true(pool4_get_any(IPPROTO_UDP, 0, &result), ""); success &= assert_equals_ipv4(&expected_ips[0], &result.address, ""); success &= assert_false(ports[0][result.l4_id], ""); ports[0][result.l4_id] = true; success &= assert_false(pool4_get_any(IPPROTO_UDP, 0, &result), ""); if (!success) return success; /* * Do the same to the second address. Use get_similar() instead of get_any() to add some quick * noise. */ result.address = expected_ips[1]; result.l4_id = 1000; success &= assert_true(pool4_return(IPPROTO_UDP, &result), "Return"); ports[1][result.l4_id] = false; if (!success) return success; query.address = expected_ips[1]; query.l4_id = 0; success &= assert_true(pool4_get_similar(IPPROTO_UDP, &query, &result), ""); success &= assert_equals_ipv4(&expected_ips[1], &result.address, ""); success &= assert_false(ports[1][result.l4_id], ""); ports[1][result.l4_id] = true; success &= assert_false(pool4_get_similar(IPPROTO_UDP, &query, &result), ""); if (!success) return success; /* Return some more stuff at once. */ result.address = expected_ips[0]; result.l4_id = 46; success &= assert_true(pool4_return(IPPROTO_UDP, &result), "Return Addr1-port46"); ports[0][46] = false; result.l4_id = 1000; success &= assert_true(pool4_return(IPPROTO_UDP, &result), "Return Addr1-port1000"); ports[0][1000] = false; result.address = expected_ips[1]; result.l4_id = 0; success &= assert_true(pool4_return(IPPROTO_UDP, &result), "ReReturn Addr2-port0"); ports[1][0] = false; if (!success) return success; /* Reborrow it. */ success &= assert_true(pool4_get_any(IPPROTO_UDP, 24, &result), "Reborrow Addr1-res-port24"); success &= assert_equals_ipv4(&expected_ips[0], &result.address, ""); success &= assert_false(ports[0][result.l4_id], ""); ports[0][result.l4_id] = true; query.address = expected_ips[0]; query.l4_id = 100; success &= assert_true(pool4_get_similar(IPPROTO_UDP, &query, &result), "Reborrow Addr1-res-port100"); success &= assert_equals_ipv4(&expected_ips[0], &result.address, ""); success &= assert_false(ports[0][result.l4_id], ""); ports[0][result.l4_id] = true; success &= assert_true(pool4_get_any(IPPROTO_UDP, 56, &result), "ReReborrow Addr2-res-port56"); success &= assert_equals_ipv4(&expected_ips[1], &result.address, ""); success &= assert_false(ports[1][result.l4_id], ""); ports[1][result.l4_id] = true; success &= assert_false(pool4_get_any(IPPROTO_UDP, 12, &result), ""); if (!success) return success; /* Now return everything. */ for (addr_ctr = 0; addr_ctr < ARRAY_SIZE(expected_ips); addr_ctr++) { result.address = expected_ips[addr_ctr]; for (port_ctr = 0; port_ctr < 1024; port_ctr += 2) { result.l4_id = port_ctr; success &= assert_true(pool4_return(IPPROTO_UDP, &result), ""); ports[addr_ctr][port_ctr] = false; } } success &= assert_false(pool4_return(IPPROTO_UDP, &result), ""); return success; }
static bool test_allocate_ipv4_transport_address(void) { struct tuple client1tuple, client2tuple, client3tuple; struct in_addr client1addr4, client2addr4, client3addr4; struct tuple *sharing_client_tuple; struct in_addr *non_sharing_addr; struct ipv4_transport_addr result; struct host6_node *host6; unsigned int i = 0; bool success = true; if (is_error(init_ipv6_tuple(&client1tuple, "1::1", 60000, "64:ff9b::1", 60000, L4PROTO_UDP))) goto fail; if (is_error(init_ipv6_tuple(&client2tuple, "1::2", 60000, "64:ff9b::2", 60000, L4PROTO_UDP))) goto fail; if (is_error(init_ipv6_tuple(&client3tuple, "1::3", 60000, "64:ff9b::3", 60000, L4PROTO_UDP))) goto fail; log_debug("IPv6 client 1 arrives and makes 25 connections."); /* * Because it's the same IPv6 client, all of those connections should be masked with the same * IPv4 address. This minimizes confusion from remote IPv4 hosts. */ client1tuple.src.addr6.l4 = 65511; if (!test_allocate_aux(&client1tuple, NULL, &client1addr4, true)) goto fail; for (i = 65512; i < 65536; i++) { client1tuple.src.addr6.l4 = i; if (!test_allocate_aux(&client1tuple, &client1addr4, NULL, true)) goto fail; } log_debug("Client 2 arrives and make 50 connections."); /* * All of them should share the same IPv4 address, * which should be different from client1's (again, to minimize confusion). */ client2tuple.src.addr6.l4 = 65486; success &= test_allocate_aux(&client2tuple, NULL, &client2addr4, true); success &= assert_true(client1addr4.s_addr != client2addr4.s_addr, "the nodes are being masked with different addresses"); if (!success) goto fail; for (i = 65487; i < 65536; i++) { client2tuple.src.addr6.l4 = i; if (!test_allocate_aux(&client2tuple, &client2addr4, NULL, true)) goto fail; } log_debug("Client 1 makes another 25 connections."); /* * Because there are still ports available, he should still get the same IPv4 address. * Essentially, this proves that client2's intervention doesn't affect client 1's connections. */ for (i = 65486; i < 65511; i++) { client1tuple.src.addr6.l4 = i; if (!test_allocate_aux(&client1tuple, &client1addr4, NULL, true)) goto fail; } log_debug("Client 3 arrives and hogs up all of its address's low ports."); /* * At this point, both IPv4 addresses have 50 high ports taken. * Because both IPv4 addresses are taken, client 3 will share its IPv4 address with someone. */ client3tuple.src.addr6.l4 = 0; if (!test_allocate_aux(&client3tuple, NULL, &client3addr4, true)) goto fail; for (i = 1; i < 1024; i++) { client3tuple.src.addr6.l4 = i; if (!test_allocate_aux(&client3tuple, &client3addr4, NULL, true)) goto fail; } log_debug("The client that shares an address with client 3 requests a low port."); /* * Because all of them are taken, he gets the same address but a runner-up high port instead. */ if (addr4_equals(&client1addr4, &client3addr4)) { sharing_client_tuple = &client1tuple; non_sharing_addr = &client2addr4; } else if (addr4_equals(&client2addr4, &client3addr4)) { sharing_client_tuple = &client2tuple; non_sharing_addr = &client1addr4; } else { log_err("Client 3 doesn't share its IPv4 address with anyone, despite validations."); goto fail; } if (is_error(host6_node_get_or_create(&sharing_client_tuple->src.addr6.l3, &host6))) goto fail; sharing_client_tuple->src.addr6.l4 = 0; success &= assert_equals_int(0, allocate_transport_address(host6, sharing_client_tuple, &result), "result 3"); success &= assert_equals_ipv4(&client3addr4, &result.l3, "runnerup still gets his addr"); success &= assert_true(is_high(result.l4), "runnerup gets a high port"); success &= bib_inject(&sharing_client_tuple->src.addr6.l3, sharing_client_tuple->src.addr6.l4, &result.l3, result.l4, L4PROTO_UDP) != NULL; host6_node_return(host6); if (!success) goto fail; log_debug("Client 3 now hogs up all of its address's remaining ports."); /* 51 high ports were already taken, so this will stop early. */ for (i = 1024; i < 65485; i++) { client3tuple.src.addr6.l4 = i; if (!test_allocate_aux(&client3tuple, &client3addr4, NULL, i != 65484)) goto fail; } /* * At this point, client's address has 50 + 1024 + 1 + 64461 = 65536 ports taken. * ie. It no longer has any ports. */ log_debug("Then, the function will fall back to use the other address."); client3tuple.src.addr6.l4 = i; if (is_error(host6_node_get_or_create(&client3tuple.src.addr6.l3, &host6))) goto fail; success &= assert_equals_int(0, allocate_transport_address(host6, &client3tuple, &result), "function result"); success &= assert_true(client3addr4.s_addr != result.l3.s_addr, "node gets a runnerup address"); success &= bib_inject(&client3tuple.src.addr6.l3, client3tuple.src.addr6.l4, &result.l3, result.l4, L4PROTO_UDP) != NULL; host6_node_return(host6); if (!success) goto fail; log_debug("It will also fall back to use the other address as the other sharing node now hogs " "all remaining ports."); /* * 51 high ports ports were already taken, so this will stop early. * Also, the sharing client already requested port 0, so we have to start at 1. */ for (i = 1; i < 65486; i++) { sharing_client_tuple->src.addr6.l4 = i; if (!test_allocate_aux(sharing_client_tuple, non_sharing_addr, NULL, i != 65485)) goto fail; } log_debug("Now the pool is completely exhausted, so further requests cannot fall back."); if (is_error(host6_node_get_or_create(&client1tuple.src.addr6.l3, &host6))) goto fail; success &= assert_equals_int(-ESRCH, allocate_transport_address(host6, &client1tuple, &result), "client 1's request is denied"); host6_node_return(host6); if (is_error(host6_node_get_or_create(&client2tuple.src.addr6.l3, &host6))) goto fail; success &= assert_equals_int(-ESRCH, allocate_transport_address(host6, &client2tuple, &result), "client 2's request is denied"); host6_node_return(host6); if (is_error(host6_node_get_or_create(&client3tuple.src.addr6.l3, &host6))) goto fail; success &= assert_equals_int(-ESRCH, allocate_transport_address(host6, &client3tuple, &result), "client 3's request is denied"); host6_node_return(host6); return success; fail: log_debug("i was %u.", i); return false; }