On 9/22/26 3:18 PM, Mike Pattrick via dev wrote:
> The source address parameter in ovs_router_lookup is both an input and
> an output. However, the interface was complex. The caller could
> inadvertently set a search over v4 or v6 rules based on if the source
> address was initialized to in6addr_any or in6addr_v4mapped_any. The
> lookup function even used these two values interchangeably.
>
> This patch uses dst address to determine if the lookup is v4 or v6, and
> considers both v6_any and v4mapped_any to be the null value equally. Now
> if the caller just wants src as output, they can initialize it to v6_any
> and lookup will still work correctly.
>
> Fixes: dc14e92bcc25 ("route-table: Introduce multi-table route lookup.")
> Signed-off-by: Mike Pattrick <[email protected]>
> ---
> v2:
> - Split src parameter into in and out versions in lookup function.
> - Big refactor of lookup function.
> - Added comment to explain their use.
> - Changed unit test formatting.
> - Added address family to router classifier.
> v3:
> - Split ipv4 addresses out of ipv6_dst field
> - Updated comments
> v4:
> - Removed assert, replaced with conditional
> v5:
> - Convered rt_init_match to match_* functions
> - Renamed variables to in/out suffix
> - Updated prototypes and comments
> ---
> lib/flow.c | 2 +-
> lib/netdev-vport.c | 2 +-
> lib/ovs-router.c | 146 ++++++++++++++++++++---------------
> lib/ovs-router.h | 6 +-
> ofproto/ofproto-dpif-sflow.c | 2 +-
> ofproto/ofproto-dpif-xlate.c | 30 ++++---
> tests/ovs-router.at | 22 ++++++
> 7 files changed, 129 insertions(+), 81 deletions(-)
>
> diff --git a/lib/flow.c b/lib/flow.c
> index a59a25c46..d14603fe3 100644
> --- a/lib/flow.c
> +++ b/lib/flow.c
> @@ -3731,7 +3731,7 @@ flow_get_tunnel_netdev(struct flow_tnl *tunnel)
> return NULL;
> }
>
> - if (!ovs_router_lookup(0, &ip6, iface, NULL, &gw)) {
> + if (!ovs_router_lookup(0, &ip6, NULL, iface, NULL, &gw)) {
> return NULL;
> }
>
> diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
> index d11269d00..aa5223128 100644
> --- a/lib/netdev-vport.c
> +++ b/lib/netdev-vport.c
> @@ -300,7 +300,7 @@ tunnel_check_status_change__(struct netdev_vport *netdev)
> iface[0] = '\0';
> route = &tnl_cfg->ipv6_dst;
> mark = tnl_cfg->egress_pkt_mark;
> - if (ovs_router_lookup(mark, route, iface, NULL, &gw)) {
> + if (ovs_router_lookup(mark, route, NULL, iface, NULL, &gw)) {
> struct netdev *egress_netdev;
>
> if (!netdev_open(iface, NULL, &egress_netdev)) {
> diff --git a/lib/ovs-router.c b/lib/ovs-router.c
> index 2566386ea..f3ceb40e0 100644
> --- a/lib/ovs-router.c
> +++ b/lib/ovs-router.c
> @@ -173,90 +173,103 @@ ovs_router_lookup_fallback(const struct in6_addr
> *ip6_dst,
> return true;
> }
>
> +/* If ip6_src is set, ip6_dst will be set based on it instead of the route
> + * entry. */
This comment doesn't match the behavior. Should be out_src.
> bool
> ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
> - char output_netdev[],
> - struct in6_addr *src, struct in6_addr *gw)
> + const struct in6_addr *ip6_src, char output_netdev[],
> + struct in6_addr *out_src, struct in6_addr *gw)
> {
> - struct flow flow = {.ipv6_dst = *ip6_dst, .pkt_mark = mark};
> - const struct in6_addr *from_src = src;
> - const struct cls_rule *cr = NULL;
> + bool is_ipv4 = IN6_IS_ADDR_V4MAPPED(ip6_dst);
> + ovs_be16 dl_type = is_ipv4 ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6);
> + const struct cls_rule *cr;
> struct router_rule *rule;
> + struct classifier *cls;
> + struct flow flow;
>
> - if (src && ipv6_addr_is_set(src)) {
> - struct flow flow_src = {.ipv6_dst = *src, .pkt_mark = mark};
> - struct classifier *cls_local = cls_find(CLS_LOCAL);
> - const struct cls_rule *cr_src;
> -
> - if (!cls_local) {
> + if (ip6_src) {
> + if (is_ipv4 != IN6_IS_ADDR_V4MAPPED(ip6_src)) {
> return false;
> }
>
> - cr_src = classifier_lookup(cls_local, OVS_VERSION_MAX, &flow_src,
> - NULL, NULL);
> - if (!cr_src) {
> + if (is_ipv4) {
> + flow = (struct flow) {.nw_dst =
> in6_addr_get_mapped_ipv4(ip6_src),
> + .pkt_mark = mark, .dl_type = dl_type};
> + } else {
> + flow = (struct flow) {.ipv6_dst = *ip6_src, .pkt_mark = mark,
> + .dl_type = dl_type};
> + }
> +
> + cls = cls_find(CLS_LOCAL);
> +
> + if (!cls) {
> return false;
> }
> - }
>
> - if (!from_src) {
> - if (IN6_IS_ADDR_V4MAPPED(ip6_dst)) {
> - from_src = &in6addr_v4mapped_any;
> + cr = classifier_lookup(cls, OVS_VERSION_MAX, &flow, NULL, NULL);
> + if (!cr) {
> + return false;
> + }
> + if (out_src) {
> + *out_src = *ip6_src;
> + out_src = NULL;
> + }
> + } else {
> + if (is_ipv4) {
> + ip6_src = &in6addr_v4mapped_any;
> } else {
> - from_src = &in6addr_any;
> + ip6_src = &in6addr_any;
> }
> }
>
> + if (is_ipv4) {
> + flow = (struct flow) {.nw_dst = in6_addr_get_mapped_ipv4(ip6_dst),
> + .pkt_mark = mark, .dl_type = dl_type};
> + } else {
> + flow = (struct flow) {.ipv6_dst = *ip6_dst, .pkt_mark = mark,
> + .dl_type = dl_type};
> + }
> +
> PVECTOR_FOR_EACH (rule, &rules) {
> uint8_t plen = rule->ipv4 ? rule->src_prefix + 96 : rule->src_prefix;
> bool matched;
>
> - if ((IN6_IS_ADDR_V4MAPPED(from_src) && !rule->ipv4) ||
> - (!IN6_IS_ADDR_V4MAPPED(from_src) && rule->ipv4)) {
> + if (is_ipv4 != rule->ipv4) {
> continue;
> }
>
> matched = (!rule->src_prefix ||
> - ipv6_addr_equals_masked(&rule->from_addr, from_src,
> plen));
> + ipv6_addr_equals_masked(&rule->from_addr, ip6_src, plen));
>
> if (rule->invert) {
> matched = !matched;
> }
>
> - if (matched) {
> - struct classifier *cls = cls_find(rule->lookup_table);
> + if (!matched) {
> + continue;
> + }
> + cls = cls_find(rule->lookup_table);
>
> - if (!cls) {
> - /* A rule can be added before the table is created. */
> - continue;
> - }
> - cr = classifier_lookup(cls, OVS_VERSION_MAX, &flow, NULL,
> - NULL);
> - if (cr) {
> - struct ovs_router_entry *p = ovs_router_entry_cast(cr);
> - /* Avoid matching mapped IPv4 of a packet against default
> IPv6
> - * route entry. Either packet dst is IPv6 or both packet and
> - * route entry dst are mapped IPv4.
> - */
> - if (!IN6_IS_ADDR_V4MAPPED(ip6_dst) ||
> - IN6_IS_ADDR_V4MAPPED(&p->nw_addr)) {
> - break;
> - }
> - }
> + if (!cls) {
> + /* A rule can be added before the table is created. */
> + continue;
> + }
> + cr = classifier_lookup(cls, OVS_VERSION_MAX, &flow, NULL, NULL);
> + if (!cr) {
> + continue;
> }
> - }
>
> - if (cr) {
> struct ovs_router_entry *p = ovs_router_entry_cast(cr);
>
> ovs_strlcpy(output_netdev, p->output_netdev, IFNAMSIZ);
> *gw = p->gw;
> - if (src && !ipv6_addr_is_set(src)) {
> - *src = p->src_addr;
> + if (out_src) {
> + *out_src = p->src_addr;
> }
> return true;
> }
> - return ovs_router_lookup_fallback(ip6_dst, output_netdev, src, gw);
> +
> + return ovs_router_lookup_fallback(ip6_dst, output_netdev, out_src, gw);
> }
>
> static void
> @@ -270,17 +283,20 @@ static void rt_init_match(struct match *match, uint32_t
> mark,
> const struct in6_addr *ip6_dst,
> uint8_t plen)
> {
> - struct in6_addr dst;
> - struct in6_addr mask;
>
> - mask = ipv6_create_mask(plen);
> + match_init_catchall(match);
> + match_set_pkt_mark(match, mark);
>
> - dst = ipv6_addr_bitand(ip6_dst, &mask);
> - memset(match, 0, sizeof *match);
> - match->flow.ipv6_dst = dst;
> - match->wc.masks.ipv6_dst = mask;
> - match->wc.masks.pkt_mark = UINT32_MAX;
> - match->flow.pkt_mark = mark;
> + if (IN6_IS_ADDR_V4MAPPED(ip6_dst)) {
> + match_set_dl_type(match, htons(ETH_TYPE_IP));
> + match_set_nw_dst_masked(match, in6_addr_get_mapped_ipv4(ip6_dst),
> + be32_prefix_mask(plen - 96));
Looks like this is will go negative is someone passes a mapped v4 address
with the prefix length below 96 into appctl. Maybe we can fail the parsing
for such input, i.e. fail the scan_ipv6_route() ? Having plen < 96 isn't
really meaningful and should not occur in any real use case.
Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev