On 8/27/26 7:46 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
> ---
> lib/flow.c | 2 +-
> lib/netdev-vport.c | 2 +-
> lib/ovs-router.c | 155 +++++++++++++++++++++--------------
> lib/ovs-router.h | 2 +-
> ofproto/ofproto-dpif-sflow.c | 2 +-
> ofproto/ofproto-dpif-xlate.c | 30 ++++---
> tests/ovs-router.at | 22 +++++
> 7 files changed, 139 insertions(+), 76 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..bcc2ceda9 100644
> --- a/lib/ovs-router.c
> +++ b/lib/ovs-router.c
> @@ -173,90 +173,104 @@ ovs_router_lookup_fallback(const struct in6_addr
> *ip6_dst,
> return true;
> }
>
> +/* If src_in is set, src_out will be set based on it instead of the route
There is no 'src_in' in this fucntion anymore.
> + * entry. */
> bool
> ovs_router_lookup(uint32_t mark, const struct in6_addr *ip6_dst,
> + const struct in6_addr *ip6_src,
> char output_netdev[],
> - struct in6_addr *src, struct in6_addr *gw)
> + struct in6_addr *src_out, 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 (src_out) {
> + *src_out = *ip6_src;
> + src_out = 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 (src_out) {
> + *src_out = 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, src_out, gw);
> }
>
> static void
> @@ -270,17 +284,34 @@ 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);
>
> - dst = ipv6_addr_bitand(ip6_dst, &mask);
> memset(match, 0, sizeof *match);
This function grew too much for us to not use the proper API.
This should probably become the match_init_catchall(match).
> - match->flow.ipv6_dst = dst;
> - match->wc.masks.ipv6_dst = mask;
> match->wc.masks.pkt_mark = UINT32_MAX;
> + match->wc.masks.dl_type = htons(UINT16_MAX);
OVS_BE16_MAX, but may also delay this to the per-family match_set.
> match->flow.pkt_mark = mark;
match_set_pkt_mark(match, mark);
> +
> + if (IN6_IS_ADDR_V4MAPPED(ip6_dst)) {
> + ovs_be32 dst = in6_addr_get_mapped_ipv4(ip6_dst);
> + ovs_be32 mask = OVS_BE32_MAX;
> +
> + plen -= 96;
> + if (plen < 32) {
> + mask ^= htonl(0xFFFFFFFF >> plen);
be32_prefix_mask(plen - 96) ?
Also, do we need that 'if'? It's an optimization for /32, right?
Not sure if that's important. This function is not used in packet
processing to care about a couple cycles in a special case.
> + }
> +
> + match->flow.nw_dst = dst & mask;
> + match->flow.dl_type = htons(ETH_TYPE_IP);
> + match->wc.masks.nw_dst = mask;
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));
> + } else {
> + struct in6_addr dst;
> + struct in6_addr mask;
> +
> + mask = ipv6_create_mask(plen);
> + dst = ipv6_addr_bitand(ip6_dst, &mask);
> + match->flow.ipv6_dst = dst;
> + match->flow.dl_type = htons(ETH_TYPE_IPV6);
> + match->wc.masks.ipv6_dst = mask;
match_set_dl_type(match, htons(ETH_TYPE_IPV6));
match_set_ipv6_dst_masked(match, ip6_dst, ipv6_create_mask(plen));
> + }
> }
>
> static int
> @@ -396,7 +427,11 @@ ovs_router_insert__(uint32_t table, uint32_t mark,
> uint8_t priority,
> p->gw = *gw;
> }
> p->mark = mark;
> - p->nw_addr = match.flow.ipv6_dst;
> + if (match.flow.dl_type == htons(ETH_TYPE_IPV6)) {
> + p->nw_addr = match.flow.ipv6_dst;
> + } else {
> + in6_addr_set_mapped_ipv4(&p->nw_addr, match.flow.nw_dst);
> + }
> p->plen = plen;
> p->user = user;
> p->priority = priority;
> @@ -1129,7 +1164,8 @@ static void
> ovs_router_lookup_cmd(struct unixctl_conn *conn, int argc,
> const char *argv[], void *aux OVS_UNUSED)
> {
> - struct in6_addr gw, src6 = in6addr_any;
> + struct in6_addr gw, src_out, src6;
> + struct in6_addr *src_in = NULL;
> char src6_s[IPV6_SCAN_LEN + 1];
> char iface[IFNAMSIZ];
> struct in6_addr ip6;
> @@ -1159,10 +1195,13 @@ ovs_router_lookup_cmd(struct unixctl_conn *conn, int
> argc,
> if (is_ipv6) {
> if (ovs_scan(argv[i], "src="IPV6_SCAN_FMT, src6_s) &&
> ipv6_parse(src6_s, &src6)) {
> + src_in = &src6;
> continue;
> }
> } else {
> if (ovs_scan(argv[i], "src="IP_SCAN_FMT, IP_SCAN_ARGS(&src))) {
> + in6_addr_set_mapped_ipv4(&src6, src);
> + src_in = &src6;
> continue;
> }
> }
> @@ -1171,15 +1210,11 @@ ovs_router_lookup_cmd(struct unixctl_conn *conn, int
> argc,
> return;
> }
>
> - if (src) {
> - in6_addr_set_mapped_ipv4(&src6, src);
> - }
> -
> - if (ovs_router_lookup(mark, &ip6, iface, &src6, &gw)) {
> + if (ovs_router_lookup(mark, &ip6, src_in, iface, &src_out, &gw)) {
> struct ds ds = DS_EMPTY_INITIALIZER;
>
> ds_put_format(&ds, "src ");
> - ipv6_format_mapped(&src6, &ds);
> + ipv6_format_mapped(&src_out, &ds);
> ds_put_format(&ds, "\ngateway ");
> ipv6_format_mapped(&gw, &ds);
> ds_put_format(&ds, "\ndev %s\n", iface);
> diff --git a/lib/ovs-router.h b/lib/ovs-router.h
> index bd1ab7a9a..ff465a7c7 100644
> --- a/lib/ovs-router.h
> +++ b/lib/ovs-router.h
> @@ -34,7 +34,7 @@ enum {
> };
>
> bool ovs_router_lookup(uint32_t mark, const struct in6_addr *ip_dst,
> - char output_netdev[],
> + const struct in6_addr *src_in, char output_netdev[],
> struct in6_addr *src, struct in6_addr *gw);
Names changed.
> void ovs_router_init(void);
> bool ovs_router_is_referenced(uint32_t table);
> diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
> index e043d7cbc..27e5955a6 100644
> --- a/ofproto/ofproto-dpif-sflow.c
> +++ b/ofproto/ofproto-dpif-sflow.c
> @@ -482,7 +482,7 @@ sflow_choose_agent_address(const char *agent_device,
>
> struct in6_addr gw, src = in6addr_any;
> char name[IFNAMSIZ];
> - if (ovs_router_lookup(0, &target_ip, name, &src, &gw)) {
> + if (ovs_router_lookup(0, &target_ip, NULL, name, &src, &gw)) {
> ip = src;
> goto success;
> }
> diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
> index 764dbd662..cb4467708 100644
> --- a/ofproto/ofproto-dpif-xlate.c
> +++ b/ofproto/ofproto-dpif-xlate.c
> @@ -3691,16 +3691,28 @@ process_special(struct xlate_ctx *ctx, const struct
> xport *xport)
> static int
> tnl_route_lookup_flow(const struct xlate_ctx *ctx,
> const struct flow *oflow,
> - struct in6_addr *ip, struct in6_addr *src,
> + struct in6_addr *ip, struct in6_addr *out_src,
> struct xport **out_port)
> {
> - char out_dev[IFNAMSIZ];
> struct xbridge *xbridge;
> - struct in6_addr gw;
> + char out_dev[IFNAMSIZ];
> + struct in6_addr in_src;
> struct in6_addr dst;
> + struct in6_addr gw;
> + bool have_src = false;
> +
> + /* Use source address for the route lookup, if provided. */
> + if (oflow->tunnel.ip_src) {
> + in6_addr_set_mapped_ipv4(&in_src, oflow->tunnel.ip_src);
> + have_src = true;
> + } else if (ipv6_addr_is_set(&oflow->tunnel.ipv6_src)) {
> + in_src = oflow->tunnel.ipv6_src;
> + have_src = true;
> + }
>
> dst = flow_tnl_dst(&oflow->tunnel);
> - if (!ovs_router_lookup(oflow->pkt_mark, &dst, out_dev, src, &gw)) {
> + if (!ovs_router_lookup(oflow->pkt_mark, &dst, have_src ? &in_src : NULL,
> + out_dev, out_src, &gw)) {
in/out are prefixes here, but suffixes in ovs_router_lookup_cmd. While these
are different functions, it may be better to use unified names.
> return -ENOENT;
> }
>
> @@ -3890,8 +3902,8 @@ native_tunnel_output(struct xlate_ctx *ctx, const
> struct xport *xport,
> struct ovs_action_push_tnl tnl_push_data;
> struct xport *out_dev = NULL;
> ovs_be32 s_ip = 0, d_ip = 0;
> - struct in6_addr s_ip6 = in6addr_any;
> - struct in6_addr d_ip6 = in6addr_any;
> + struct in6_addr s_ip6;
> + struct in6_addr d_ip6;
> struct eth_addr smac;
> struct eth_addr dmac;
> int err;
> @@ -3909,12 +3921,6 @@ native_tunnel_output(struct xlate_ctx *ctx, const
> struct xport *xport,
> memcpy(&old_base_flow, &ctx->base_flow, sizeof old_base_flow);
> memcpy(&old_flow, &ctx->xin->flow, sizeof old_flow);
>
> - if (flow->tunnel.ip_src) {
> - in6_addr_set_mapped_ipv4(&s_ip6, flow->tunnel.ip_src);
> - } else if (ipv6_addr_is_set(&flow->tunnel.ipv6_src)) {
> - s_ip6 = flow->tunnel.ipv6_src;
> - }
> -
> err = tnl_route_lookup_flow(ctx, flow, &d_ip6, &s_ip6, &out_dev);
> if (err) {
> put_cloned_drop_action(ctx->xbridge->ofproto, ctx->odp_actions,
> diff --git a/tests/ovs-router.at b/tests/ovs-router.at
> index 97eb95fad..a45f09db4 100644
> --- a/tests/ovs-router.at
> +++ b/tests/ovs-router.at
> @@ -310,6 +310,28 @@ OVS_VSWITCHD_START([add-port br0 p1 -- set Interface p1
> type=dummy])
> AT_CHECK([ovs-appctl netdev-dummy/ip4addr br0 192.0.2.1/24], [0], [OK
> ])
>
> +AT_CHECK([ovs-appctl ovs/route/rule/add from=all table=15], [0], [OK
> +])
> +AT_CHECK([ovs-appctl ovs/route/add 2.2.2.3/32 br0 192.0.2.1 table=15], [0],
> [OK
> +])
> +
> +AT_CHECK([ovs-appctl ovs/route/show table=all | sort], [0], [dnl
> +Cached: 192.0.2.0/24 dev br0 SRC 192.0.2.1
> +Cached: 192.0.2.1/32 dev br0 SRC 192.0.2.1 local
> +User: 2.2.2.3/32 dev br0 GW 192.0.2.1 SRC 192.0.2.1 table 15
> +])
> +
> +AT_CHECK([ovs-appctl ovs/route/lookup 2.2.2.3], [0], [dnl
> +src 192.0.2.1
> +gateway 192.0.2.1
> +dev br0
> +])
> +
> +AT_CHECK([ovs-appctl ovs/route/del 2.2.2.3/32 table=15], [0], [OK
> +])
> +AT_CHECK([ovs-appctl ovs/route/rule/del from=all table=15], [0], [OK
> +])
> +
> AT_CHECK([ovs-appctl ovs/route/add 10.1.1.0/24 br0 192.0.2.2 table=11], [0],
> [OK
> ])
> AT_CHECK([ovs-appctl ovs/route/add 10.2.2.0/24 br0 192.0.2.2 table=12], [0],
> [OK
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev