diff --git a/include/dp_lpm.h b/include/dp_lpm.h index 9659ea01b..ecc23efa5 100644 --- a/include/dp_lpm.h +++ b/include/dp_lpm.h @@ -43,13 +43,13 @@ const struct dp_port *dp_get_ip4_out_port(const struct dp_port *in_port, uint32_t t_vni, const struct dp_flow *df, struct dp_iface_route *route, - uint32_t *p_ip); + bool *is_default_route); const struct dp_port *dp_get_ip6_out_port(const struct dp_port *in_port, uint32_t t_vni, const struct dp_flow *df, struct dp_iface_route *route, - union dp_ipv6 *p_ipv6); + bool *is_default_route); uint32_t dp_get_gw_ip4(void); const union dp_ipv6 *dp_get_gw_ip6(void); diff --git a/src/dp_lpm.c b/src/dp_lpm.c index a6ccfc967..6bfa5a7e0 100644 --- a/src/dp_lpm.c +++ b/src/dp_lpm.c @@ -278,13 +278,14 @@ const struct dp_port *dp_get_ip4_out_port(const struct dp_port *in_port, uint32_t t_vni, const struct dp_flow *df, struct dp_iface_route *route, - uint32_t *p_ip) + bool *is_default_route) { uint32_t dst_ip = ntohl(df->dst.dst_addr); struct rte_rib_node *node; struct rte_rib *root; uint64_t next_hop; struct dp_port *dst_port; + uint8_t route_depth; if (t_vni == 0) t_vni = in_port->iface.vni; @@ -307,9 +308,12 @@ const struct dp_port *dp_get_ip4_out_port(const struct dp_port *in_port, if (dst_port->is_pf) rte_memcpy(route, rte_rib_get_ext(node), sizeof(*route)); - if (DP_FAILED(rte_rib_get_ip(node, p_ip))) + if (DP_FAILED(rte_rib_get_depth(node, &route_depth))) return NULL; + // Normally we should match prefix/length, but for length 0 the ip is implied + *is_default_route = route_depth == 0; + return dst_port; } @@ -317,13 +321,13 @@ const struct dp_port *dp_get_ip6_out_port(const struct dp_port *in_port, uint32_t t_vni, const struct dp_flow *df, struct dp_iface_route *route, - union dp_ipv6 *p_ipv6) + bool *is_default_route) { struct rte_rib6_node *node; struct rte_rib6 *root; uint64_t next_hop; struct dp_port *dst_port; - struct rte_ipv6_addr ip; + uint8_t route_depth; if (t_vni == 0) t_vni = in_port->iface.vni; @@ -346,9 +350,11 @@ const struct dp_port *dp_get_ip6_out_port(const struct dp_port *in_port, if (dst_port->is_pf) rte_memcpy(route, rte_rib6_get_ext(node), sizeof(*route)); - if (DP_FAILED(rte_rib6_get_ip(node, &ip))) + if (DP_FAILED(rte_rib6_get_depth(node, &route_depth))) return NULL; - dp_ipv6_from_rte(p_ipv6, &ip); + // Normally we should match prefix/length, but for length 0 the ip is implied + *is_default_route = route_depth == 0; + return dst_port; } diff --git a/src/nodes/ipv4_lookup_node.c b/src/nodes/ipv4_lookup_node.c index c25ca39ee..037633635 100644 --- a/src/nodes/ipv4_lookup_node.c +++ b/src/nodes/ipv4_lookup_node.c @@ -27,7 +27,7 @@ static __rte_always_inline rte_edge_t get_next_index(__rte_unused struct rte_nod { struct dp_flow *df = dp_get_flow_ptr(m); struct dp_iface_route route; - uint32_t ip = 0; + bool is_default_route; const struct dp_port *in_port = dp_get_in_port(m); const struct dp_port *out_port; @@ -35,7 +35,7 @@ static __rte_always_inline rte_edge_t get_next_index(__rte_unused struct rte_nod if (df->l4_type == IPPROTO_UDP && df->l4_info.trans_port.dst_port == htons(DP_BOOTP_SRV_PORT)) return IPV4_LOOKUP_NEXT_DHCP; - out_port = dp_get_ip4_out_port(in_port, df->tun_info.dst_vni, df, &route, &ip); + out_port = dp_get_ip4_out_port(in_port, df->tun_info.dst_vni, df, &route, &is_default_route); if (!out_port) return IPV4_LOOKUP_NEXT_DROP; @@ -53,7 +53,7 @@ static __rte_always_inline rte_edge_t get_next_index(__rte_unused struct rte_nod if (!in_port->is_pf) df->tun_info.dst_vni = route.vni; - df->flow_type = ip == 0 ? DP_FLOW_SOUTH_NORTH : DP_FLOW_WEST_EAST; + df->flow_type = is_default_route ? DP_FLOW_SOUTH_NORTH : DP_FLOW_WEST_EAST; df->nxt_hop = out_port->port_id; // always valid since coming from struct dp_port return IPV4_LOOKUP_NEXT_NAT; diff --git a/src/nodes/ipv6_lookup_node.c b/src/nodes/ipv6_lookup_node.c index 83be15bb6..5dd211ba5 100644 --- a/src/nodes/ipv6_lookup_node.c +++ b/src/nodes/ipv6_lookup_node.c @@ -23,14 +23,14 @@ static __rte_always_inline rte_edge_t get_next_index(__rte_unused struct rte_nod struct dp_flow *df = dp_get_flow_ptr(m); struct rte_ether_hdr *ether_hdr = rte_pktmbuf_mtod(m, struct rte_ether_hdr *); struct dp_iface_route route; + bool is_default_route; const struct dp_port *in_port = dp_get_in_port(m); const struct dp_port *out_port; - union dp_ipv6 ip; int t_vni; t_vni = in_port->is_pf ? df->tun_info.dst_vni : 0; - out_port = dp_get_ip6_out_port(in_port, t_vni, df, &route, &ip); + out_port = dp_get_ip6_out_port(in_port, t_vni, df, &route, &is_default_route); if (!out_port) return IPV6_LOOKUP_NEXT_DROP; @@ -47,7 +47,7 @@ static __rte_always_inline rte_edge_t get_next_index(__rte_unused struct rte_nod if (!in_port->is_pf) df->tun_info.dst_vni = route.vni; - df->flow_type = dp_is_ipv6_zero(&ip) ? DP_FLOW_SOUTH_NORTH : DP_FLOW_WEST_EAST; + df->flow_type = is_default_route ? DP_FLOW_SOUTH_NORTH : DP_FLOW_WEST_EAST; df->nxt_hop = out_port->port_id; // always valid since coming from struct dp_port return IPV6_LOOKUP_NEXT_SNAT; diff --git a/test/local/grpc_client.py b/test/local/grpc_client.py index 0f727ed82..5efc45ba9 100644 --- a/test/local/grpc_client.py +++ b/test/local/grpc_client.py @@ -91,6 +91,8 @@ def _call(self, args): or (isinstance(error, GrpcError) and expectFailure)): return None raise error + elif expectedError: + raise AssertionError(f"Error {expectedError} expected, none received") return response def _getSpec(self, args): diff --git a/test/local/test_nat.py b/test/local/test_nat.py index 8ba3e50fb..2aa970932 100644 --- a/test/local/test_nat.py +++ b/test/local/test_nat.py @@ -1,12 +1,51 @@ # SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors # SPDX-License-Identifier: Apache-2.0 +import ipaddress import pytest import threading from helpers import * +def vm_sender(dst_ip): + pkt = (Ether(dst=PF0.mac, src=VM1.mac) / + IP(dst=dst_ip, src=VM1.ip) / + UDP(dport=1234)) + delayed_sendp(pkt, VM1.tap) + +def try_ip_range(grpc_client, iprange): + net = ipaddress.ip_network(iprange, strict=False) + route = str(net) + grpc_client.addroute(vni1, route, vni1, neigh_vni1_ul_ipv6) + + if int(net.network_address) > 0: + threading.Thread(target=vm_sender, args=(str(net.network_address-1),)).start() + pkt = sniff_packet(PF0.tap, is_udp_pkt) + assert pkt[IP].src != VM1.ip, \ + "IP not translated" + + threading.Thread(target=vm_sender, args=(str(net.network_address),)).start() + pkt = sniff_packet(PF0.tap, is_udp_pkt) + assert pkt[IP].src == VM1.ip, \ + "IP translated instead of staying local" + + if int(net.broadcast_address) < 2**32 - 1: + # Need to use +2 to get around conntrack remembering it from another range + threading.Thread(target=vm_sender, args=(str(net.broadcast_address+2),)).start() + pkt = sniff_packet(PF0.tap, is_udp_pkt) + assert pkt[IP].src != VM1.ip, \ + "IP not translated" + + grpc_client.delroute(vni1, route) + +def test_nat_default_route(prepare_ifaces, grpc_client): + nat_ul_ipv6 = grpc_client.addnat(VM1.name, nat_vip, nat_local_min_port, nat_local_max_port) + try_ip_range(grpc_client, "128.0.0.0/1") + try_ip_range(grpc_client, "0.0.0.0/1") + grpc_client.delnat(VM1.name) + + def test_network_nat_external_icmp_echo(prepare_ipv4, grpc_client): nat_ul_ipv6 = grpc_client.addnat(VM1.name, nat_vip, nat_local_min_port, nat_local_max_port) external_ping(nat_vip, nat_ul_ipv6) diff --git a/test/local/test_zzz_grpc.py b/test/local/test_zzz_grpc.py index 3e6a986c5..2bbd0235e 100644 --- a/test/local/test_zzz_grpc.py +++ b/test/local/test_zzz_grpc.py @@ -119,6 +119,9 @@ def test_grpc_route(prepare_ifaces, grpc_client): grpc_client.addinterface(VM4.name, VM4.pci, 999, VM4.ip, VM4.ipv6) grpc_client.addroute(999, "1.2.3.4/24", vni1, neigh_vni1_ul_ipv6) grpc_client.expect_error(301).addroute(999, "1.2.3.4/24", vni1, neigh_vni1_ul_ipv6) + grpc_client.expect_error(301).addroute(999, "1.2.3.255/24", vni1, neigh_vni1_ul_ipv6) + grpc_client.addroute(999, "1.2.3.4/25", vni1, neigh_vni1_ul_ipv6) + grpc_client.delroute(999, "1.2.3.4/25") grpc_client.delroute(999, "1.2.3.4/24") grpc_client.expect_error(302).delroute(999, "1.2.3.4/24") grpc_client.addroute(999, "1.2.3.4/24", vni1, neigh_vni1_ul_ipv6)