When tc_netdev_flow_put() rejects a flow because the upstream
recirc chain is not yet registered (used_chains count is zero),
the flow is permanently stranded in the kernel. Return EAGAIN
instead of EOPNOTSUPP so the revalidator retries the offload
once a sibling flow registers the chain.
A new bool offload_deferred on struct dpif_op and struct udpif_key
propagates the rejection to the revalidator, which issues a retry
on the next cycle. Retries are bounded to
OFFLOAD_DEFERRED_RETRY_MSEC (2000ms) from the first failed attempt.
Fixes: 273a4fce951a ("netdev-offload-tc: Only install recirc flows if the
parent is present.")
Reported-by: Numan Siddique <[email protected]>
Signed-off-by: Eelco Chaudron <[email protected]>
---
lib/dpif-offload-tc-netdev.c | 27 +++++++------
lib/dpif-offload-tc.c | 10 ++++-
lib/dpif.c | 7 ++++
lib/dpif.h | 3 ++
ofproto/ofproto-dpif-upcall.c | 27 +++++++++++++
tests/system-offloads-traffic.at | 68 ++++++++++++++++++++++++++++++++
6 files changed, 129 insertions(+), 13 deletions(-)
diff --git a/lib/dpif-offload-tc-netdev.c b/lib/dpif-offload-tc-netdev.c
index a2f4ab8b5..3a521aee8 100644
--- a/lib/dpif-offload-tc-netdev.c
+++ b/lib/dpif-offload-tc-netdev.c
@@ -2355,17 +2355,6 @@ tc_netdev_flow_put(struct dpif *dpif, struct netdev
*netdev,
chain = key->recirc_id;
mask->recirc_id = 0;
- if (chain) {
- /* If we match on a recirculation ID, we must ensure the previous
- * flow is also in the TC datapath; otherwise, the entry is useless,
- * as the related packets will be handled by upcalls. */
- if (!ccmap_find(&used_chains, chain)) {
- VLOG_DBG_RL(&rl, "match for chain %u failed due to non-existing "
- "goto chain action", chain);
- return EOPNOTSUPP;
- }
- }
-
if (flow_tnl_dst_is_set(&key->tunnel) ||
flow_tnl_src_is_set(&key->tunnel)) {
VLOG_DBG_RL(&rl,
@@ -2687,6 +2676,22 @@ tc_netdev_flow_put(struct dpif *dpif, struct netdev
*netdev,
return EOPNOTSUPP;
}
+ if (chain && !ccmap_find(&used_chains, chain)) {
+ /* If we match on a recirculation ID, we must ensure the previous
+ * flow is also in the TC datapath; otherwise, the entry is useless,
+ * as the related packets will be handled by upcalls.
+ *
+ * Return EAGAIN rather than EOPNOTSUPP: the chain may not be
+ * registered yet because the upstream flow install is still in
+ * progress. Unlike EOPNOTSUPP, EAGAIN tells the revalidator to
+ * retry the TC offload on a subsequent cycle. The flow is still
+ * installed in the kernel datapath (dp:ovs) in either case. */
+ VLOG_DBG_RL(&rl, "match for chain %u failed due to non-existing "
+ "goto chain action",
+ chain);
+ return EAGAIN;
+ }
+
memset(&adjust_stats, 0, sizeof adjust_stats);
if (get_ufid_tc_mapping(ufid, &id) == 0) {
VLOG_DBG_RL(&rl, "updating old handle: %d prio: %d",
diff --git a/lib/dpif-offload-tc.c b/lib/dpif-offload-tc.c
index 3b84d9f20..cb996fa4a 100644
--- a/lib/dpif-offload-tc.c
+++ b/lib/dpif-offload-tc.c
@@ -579,7 +579,8 @@ tc_parse_flow_put(struct tc_offload *offload_tc, struct
dpif *dpif,
}
netdev_set_hw_info(oor_netdev, HW_INFO_TYPE_OOR, true);
}
- level = (err == ENOSPC || err == EOPNOTSUPP) ? VLL_DBG : VLL_ERR;
+ level = (err == ENOSPC || err == EOPNOTSUPP
+ || err == EAGAIN) ? VLL_DBG : VLL_ERR;
VLOG_RL(&rl, level, "failed to offload flow: %s: %s",
ovs_strerror(err),
(oor_netdev ? netdev_get_name(oor_netdev) :
@@ -705,7 +706,12 @@ tc_operate(struct dpif *dpif, const struct dpif_offload
*offload_,
break;
} /* End of 'switch (op->type)'. */
- if (error != EOPNOTSUPP && error != ENOENT) {
+ if (error == EAGAIN) {
+ /* The recirc ID matched by this flow is not yet registered in
+ * TC (no upstream flow has a goto action for this chain).
+ * Defer the offload so the revalidator retries next cycle. */
+ op->offload_deferred = true;
+ } else if (error != EOPNOTSUPP && error != ENOENT) {
/* If the operation is unsupported or the entry was not found,
* we are skipping this flow operation. Otherwise, it was
* processed and we should report the result. */
diff --git a/lib/dpif.c b/lib/dpif.c
index 1afc7c662..0b93b0fae 100644
--- a/lib/dpif.c
+++ b/lib/dpif.c
@@ -1354,10 +1354,17 @@ dpif_operate(struct dpif *dpif, struct dpif_op **ops,
size_t n_ops,
for (i = 0; i < n_ops; i++) {
struct dpif_op *op = ops[i];
op->error = EINVAL;
+ op->offload_deferred = false;
}
return;
}
+ /* Initialize offload_deferred for all ops; the offload provider sets
+ * it to true if the flow cannot be offloaded yet. */
+ for (size_t i = 0; i < n_ops; i++) {
+ ops[i]->offload_deferred = false;
+ }
+
while (n_ops > 0) {
size_t chunk;
diff --git a/lib/dpif.h b/lib/dpif.h
index 3e6a34a25..79621887c 100644
--- a/lib/dpif.h
+++ b/lib/dpif.h
@@ -782,6 +782,9 @@ int dpif_execute(struct dpif *, struct dpif_execute *);
struct dpif_op {
enum dpif_op_type type;
int error;
+ bool offload_deferred; /* True if the offload provider requires a
+ * prerequisite flow to be programmed first.
+ * The revalidator will retry on the next cycle. */
union {
struct dpif_flow_put flow_put;
struct dpif_flow_del flow_del;
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index 8e4897202..85071e852 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -50,6 +50,7 @@
#define UPCALL_MAX_BATCH 64
#define REVALIDATE_MAX_BATCH 50
#define UINT64_THREE_QUARTERS (UINT64_MAX / 4 * 3)
+#define OFFLOAD_DEFERRED_RETRY_MSEC 2000
VLOG_DEFINE_THIS_MODULE(ofproto_dpif_upcall);
@@ -347,6 +348,8 @@ struct udpif_key {
#define OFFL_REBAL_INTVL_MSEC 3000 /* dynamic offload rebalance freq */
struct netdev *in_netdev; /* in_odp_port's netdev */
bool offloaded; /* True if flow is offloaded */
+ bool offload_deferred; /* Deferred, retry next cycle. */
+ long long int offload_deferred_expire; /* Retry deadline in msec. */
uint64_t flow_pps_rate; /* Packets-Per-Second rate */
long long int flow_time; /* last pps update time */
uint64_t flow_packets; /* #pkts seen in interval */
@@ -1746,6 +1749,11 @@ handle_upcalls(struct udpif *udpif, struct upcall
*upcalls,
transition_ukey(ukey, UKEY_EVICTED);
} else if (ukey->state < UKEY_OPERATIONAL) {
transition_ukey(ukey, UKEY_OPERATIONAL);
+ if (ops[i].dop.offload_deferred) {
+ ukey->offload_deferred_expire =
+ time_msec() + OFFLOAD_DEFERRED_RETRY_MSEC;
+ ukey->offload_deferred = true;
+ }
}
ovs_mutex_unlock(&ukey->mutex);
}
@@ -1836,6 +1844,8 @@ ukey_create__(const struct nlattr *key, size_t key_len,
ukey->xcache = NULL;
ukey->offloaded = false;
+ ukey->offload_deferred = false;
+ ukey->offload_deferred_expire = 0;
ukey->in_netdev = NULL;
ukey->flow_packets = ukey->flow_backlog_packets = 0;
@@ -2552,6 +2562,18 @@ push_dp_ops(struct udpif *udpif, struct ukey_op *ops,
size_t n_ops)
for (i = 0; i < n_ops; i++) {
struct ukey_op *op = &ops[i];
+ if (op->ukey && op->dop.offload_deferred) {
+ long long int now = time_msec();
+
+ ovs_mutex_lock(&op->ukey->mutex);
+ if (now >= op->ukey->offload_deferred_expire) {
+ op->ukey->offload_deferred_expire =
+ now + OFFLOAD_DEFERRED_RETRY_MSEC;
+ }
+ op->ukey->offload_deferred = true;
+ ovs_mutex_unlock(&op->ukey->mutex);
+ }
+
if (op->dop.error) {
if (op->ukey) {
ovs_mutex_lock(&op->ukey->mutex);
@@ -2984,6 +3006,11 @@ revalidate(struct revalidator *revalidator)
/* Takes ownership of 'recircs'. */
reval_op_init(&ops[n_ops++], result, udpif, ukey, &recircs,
&odp_actions);
+ } else if (ukey->offload_deferred) {
+ ukey->offload_deferred = false;
+ if (time_msec() < ukey->offload_deferred_expire) {
+ put_op_init(&ops[n_ops++], ukey, DPIF_FP_MODIFY);
+ }
}
ovs_mutex_unlock(&ukey->mutex);
}
diff --git a/tests/system-offloads-traffic.at b/tests/system-offloads-traffic.at
index 55c26a0ce..484654cb1 100644
--- a/tests/system-offloads-traffic.at
+++ b/tests/system-offloads-traffic.at
@@ -1200,6 +1200,74 @@ OVS_WAIT_WHILE(
OVS_TRAFFIC_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([offloads - split recirc rules kernel vs offload])
+OVS_TRAFFIC_VSWITCHD_START([], [], [-- set Open_vSwitch .
other_config:hw-offload=true])
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p1, at_ns0, br0, "10.0.0.1/24")
+ADD_VETH(p2, at_ns1, br0, "10.0.0.2/24")
+
+dnl Two source IPs on the same veth guarantee the same in_port and therefore
+dnl the same recirc ID R1 for the second datapath flow.
+NS_CHECK_EXEC([at_ns0], [ip addr add 10.0.0.3/24 dev p1])
+
+AT_CHECK([ovs-vsctl -- set interface ovs-p1 ofport_request=1 \
+ -- set interface ovs-p2 ofport_request=2])
+
+AT_CHECK([ovs-appctl vlog/set dpif_offload_tc_netdev:dbg])
+
+dnl controller() is not TC-offloadable, so both datapath flows land in dp:ovs.
+AT_DATA([flows.txt], [dnl
+table=0,arp,actions=NORMAL
+table=0,priority=100,in_port=ovs-p1,ip,nw_dst=10.0.0.2,actions=controller(),ct(commit,table=1)
+table=0,priority=100,in_port=ovs-p2,ip,actions=ovs-p1
+table=1,ip,actions=ovs-p2
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -W 2 10.0.0.2 | FORMAT_PING],
[0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+AT_CHECK([ovs-appctl revalidator/wait])
+
+AT_CHECK([grep -q "match for chain .* failed due to non-existing goto chain
action" \
+ ovs-vswitchd.log])
+AT_CHECK([ovs-appctl dpctl/dump-flows --names type=ovs \
+ filter='in_port(ovs-p1),ipv4' | grep -c "in_port(ovs-p1)"], [0], [2
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows --names type=tc \
+ filter='in_port(ovs-p1),ipv4'], [0], [dnl
+])
+
+dnl Replace the rule with plain ct (TC-offloadable). Traffic from 10.0.0.3
+dnl offloads its first datapath flow to dp:tc, which registers the recirc ID.
+dnl After the fix, the second datapath flow should also be offloaded to dp:tc.
+AT_CHECK([ovs-ofctl del-flows br0 "table=0,in_port=ovs-p1,ip,nw_dst=10.0.0.2"])
+AT_DATA([flows2.txt], [dnl
+table=0,priority=100,in_port=ovs-p1,ip,nw_dst=10.0.0.2,actions=ct(commit,table=1)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows2.txt])
+
+NS_CHECK_EXEC([at_ns0], [ping -q -c 3 -i 0.3 -W 2 -I 10.0.0.3 10.0.0.2 |
FORMAT_PING], [0], [dnl
+3 packets transmitted, 3 received, 0% packet loss, time 0ms
+])
+
+AT_CHECK([ovs-appctl revalidator/wait])
+OVS_WAIT_UNTIL([ovs-appctl dpctl/dump-flows --names type=tc \
+ filter='in_port(ovs-p1),ipv4' | grep -q "actions:ovs-p2"])
+
+AT_CHECK([ovs-appctl dpctl/dump-flows --names type=tc \
+ filter='in_port(ovs-p1),ipv4' | grep -c "in_port(ovs-p1)"], [0], [2
+])
+AT_CHECK([ovs-appctl dpctl/dump-flows --names type=ovs \
+ filter='in_port(ovs-p1),ipv4' | grep "actions:ovs-p2"], [1])
+AT_CHECK([ovs-appctl dpctl/dump-flows --names type=tc \
+ filter='in_port(ovs-p1),ipv4' | grep "actions:ovs-p2"], [0],
[ignore])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([offloads - ovs-appctl dpif/offload/show - offloads enabled])
AT_KEYWORDS([dpif-offload])
OVS_TRAFFIC_VSWITCHD_START([], [],
--
2.54.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev