@@ -8374,8 +8374,9 @@ push_tnl_action(const struct dp_netdev_pmd_thread *pmd,
const struct nlattr *attr,
struct dp_packet_batch *batch)
{
- struct tx_port *tun_port;
+ const struct netdev *ingress_netdev = NULL;
const struct ovs_action_push_tnl *data;
+ struct tx_port *tun_port;
int err;
data = nl_attr_get(attr);
@@ -8385,7 +8386,20 @@ push_tnl_action(const struct dp_netdev_pmd_thread *pmd,
err = -EINVAL;
goto error;
}
- err = netdev_push_header(tun_port->port->netdev, batch, data);
+
+ if (dpif_offload_enabled() && !dp_packet_batch_is_empty(batch)) {
+ /* To avoid multiple port lookups per batch, assume that all packets
+ * in the batch originate from the same flow and therefore share the
+ * same original input port. */
+ struct tx_port *in_port = pmd_send_port_cache_lookup(
+ pmd, batch->packets[0]->md.orig_in_port);
+ if (in_port) {
+ ingress_netdev = in_port->port->netdev;
+ }
+ }
+
+ err = netdev_push_header(tun_port->port->netdev, ingress_netdev, batch,
+ data);
if (!err) {
return 0;
}
@@ -23,6 +23,7 @@
#include "dpif-offload-provider.h"
#include "dummy.h"
#include "id-fpool.h"
+#include "netdev-native-tnl.h"
#include "netdev-provider.h"
#include "odp-util.h"
#include "util.h"
@@ -613,6 +614,35 @@ dummy_offload_hw_post_process(const struct dpif_offload *offload_,
return 0;
}
+static ovs_be16
+dummy_offload_udp_tnl_get_src_port__(struct dp_packet *packet)
+{
+ /* Use FNV-1a hash to ensure consistent results across all platforms. The
+ * standard OVS hash functions have architecture-specific implementations
+ * (SSE4.2, ARM64 optimizations, etc.) that produce different outputs for
+ * identical inputs, making tests non-deterministic. */
+ const uint8_t *data = dp_packet_data(packet);
+ size_t len = dp_packet_size(packet);
+ uint32_t hash = 2166136261U;
+ uint32_t prime = 16777619U;
+
+ for (size_t i = 0; i < len; i++) {
+ hash ^= data[i];
+ hash *= prime;
+ }
+ return htons((uint16_t) hash);
+}
+
+static bool
+dummy_offload_udp_tnl_get_src_port(
+ const struct dpif_offload *offload OVS_UNUSED,
+ const struct netdev *ingress_netdev OVS_UNUSED,
+ struct dp_packet *packet, ovs_be16 *src_port)
+{
+ *src_port = dummy_offload_udp_tnl_get_src_port__(packet);
+ return true;
+}
+
static bool
dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
odp_port_t in_odp,
@@ -631,7 +661,10 @@ dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
* that they provide full protection when calling netdev_send() from any
* thread, via a netdev-level mutex. */
NL_ATTR_FOR_EACH (nla, left, actions, actions_len) {
- if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
+ enum ovs_action_attr action = nl_attr_type(nla);
+
+ switch (action) {
+ case OVS_ACTION_ATTR_OUTPUT: {
odp_port_t out_odp = nl_attr_get_odp_port(nla);
struct dummy_offload_port *out_port;
@@ -641,7 +674,49 @@ dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
netdev_get_type(out_port->pm_port.netdev))) {
return false;
}
- } else {
+ break;
+ }
+
+ case OVS_ACTION_ATTR_TUNNEL_PUSH: {
+ /* We only support UDP tunnels, i.e. VXLAN and Geneve. */
+ const struct ovs_action_push_tnl *data = nl_attr_get(nla);
+
+ if (data->tnl_type != OVS_VPORT_TYPE_VXLAN
+ && data->tnl_type != OVS_VPORT_TYPE_GENEVE) {
+ return false;
+ }
+ break;
+ }
+
+ case OVS_ACTION_ATTR_UNSPEC:
+ case OVS_ACTION_ATTR_USERSPACE:
+ case OVS_ACTION_ATTR_SET:
+ case OVS_ACTION_ATTR_PUSH_VLAN:
+ case OVS_ACTION_ATTR_POP_VLAN:
+ case OVS_ACTION_ATTR_SAMPLE:
+ case OVS_ACTION_ATTR_RECIRC:
+ case OVS_ACTION_ATTR_HASH:
+ case OVS_ACTION_ATTR_PUSH_MPLS:
+ case OVS_ACTION_ATTR_POP_MPLS:
+ case OVS_ACTION_ATTR_SET_MASKED:
+ case OVS_ACTION_ATTR_CT:
+ case OVS_ACTION_ATTR_TRUNC:
+ case OVS_ACTION_ATTR_PUSH_ETH:
+ case OVS_ACTION_ATTR_POP_ETH:
+ case OVS_ACTION_ATTR_CT_CLEAR:
+ case OVS_ACTION_ATTR_PUSH_NSH:
+ case OVS_ACTION_ATTR_POP_NSH:
+ case OVS_ACTION_ATTR_METER:
+ case OVS_ACTION_ATTR_CLONE:
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ case OVS_ACTION_ATTR_ADD_MPLS:
+ case OVS_ACTION_ATTR_DEC_TTL:
+ case OVS_ACTION_ATTR_DROP:
+ case OVS_ACTION_ATTR_PSAMPLE:
+ case OVS_ACTION_ATTR_TUNNEL_POP:
+ case OVS_ACTION_ATTR_LB_OUTPUT:
+ case __OVS_ACTION_ATTR_MAX:
+ default:
return false;
}
}
@@ -664,8 +739,10 @@ dummy_offload_hw_process_pkt(const struct dpif_offload *offload_,
NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) {
bool last_action = (left <= NLA_ALIGN(nla->nla_len));
+ enum ovs_action_attr action = nl_attr_type(nla);
- if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
+ switch (action) {
+ case OVS_ACTION_ATTR_OUTPUT: {
odp_port_t odp_port = nl_attr_get_odp_port(nla);
struct dummy_offload_port *port;
struct dp_packet_batch batch;
@@ -684,6 +761,55 @@ dummy_offload_hw_process_pkt(const struct dpif_offload *offload_,
* for now we assume hash steering based on the number of queues
* configured for the dummy-netdev. */
netdev_send(port->pm_port.netdev, hash % n_txq, &batch, false);
+ break;
+ }
+ case OVS_ACTION_ATTR_TUNNEL_PUSH: {
+ const struct ovs_action_push_tnl *data = nl_attr_get(nla);
+ struct udp_header *udp;
+ struct flow ovs_flow;
+ ovs_be16 src_port;
+
+ src_port = dummy_offload_udp_tnl_get_src_port__(pkt);
+ netdev_tnl_push_udp_header(NULL, NULL, pkt, data);
+
+ flow_extract(pkt, &ovs_flow);
+ udp = dp_packet_l4(pkt);
+ ovs_assert(ovs_flow.nw_proto == IPPROTO_UDP && udp);
+
+ udp->udp_src = src_port;
+ break;
+ }
+
+ case OVS_ACTION_ATTR_UNSPEC:
+ case OVS_ACTION_ATTR_USERSPACE:
+ case OVS_ACTION_ATTR_SET:
+ case OVS_ACTION_ATTR_PUSH_VLAN:
+ case OVS_ACTION_ATTR_POP_VLAN:
+ case OVS_ACTION_ATTR_SAMPLE:
+ case OVS_ACTION_ATTR_RECIRC:
+ case OVS_ACTION_ATTR_HASH:
+ case OVS_ACTION_ATTR_PUSH_MPLS:
+ case OVS_ACTION_ATTR_POP_MPLS:
+ case OVS_ACTION_ATTR_SET_MASKED:
+ case OVS_ACTION_ATTR_CT:
+ case OVS_ACTION_ATTR_TRUNC:
+ case OVS_ACTION_ATTR_PUSH_ETH:
+ case OVS_ACTION_ATTR_POP_ETH:
+ case OVS_ACTION_ATTR_CT_CLEAR:
+ case OVS_ACTION_ATTR_PUSH_NSH:
+ case OVS_ACTION_ATTR_POP_NSH:
+ case OVS_ACTION_ATTR_METER:
+ case OVS_ACTION_ATTR_CLONE:
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ case OVS_ACTION_ATTR_ADD_MPLS:
+ case OVS_ACTION_ATTR_DEC_TTL:
+ case OVS_ACTION_ATTR_DROP:
+ case OVS_ACTION_ATTR_PSAMPLE:
+ case OVS_ACTION_ATTR_TUNNEL_POP:
+ case OVS_ACTION_ATTR_LB_OUTPUT:
+ case __OVS_ACTION_ATTR_MAX:
+ default:
+ OVS_NOT_REACHED();
}
}
@@ -1066,6 +1192,7 @@ dummy_pmd_thread_lifecycle(const struct dpif_offload *dpif_offload,
.port_del = dummy_offload_port_del, \
.get_netdev = dummy_offload_get_netdev, \
.netdev_hw_post_process = dummy_offload_hw_post_process, \
+ .netdev_udp_tnl_get_src_port = dummy_offload_udp_tnl_get_src_port, \
.netdev_flow_put = dummy_flow_put, \
.netdev_flow_del = dummy_flow_del, \
.netdev_flow_stats = dummy_flow_stats, \
@@ -279,6 +279,19 @@ struct dpif_offload_class {
unsigned pmd_id, struct dp_packet *,
void **flow_reference);
+ /* Allows the offload provider to override the default UDP tunnel source
+ * port selection. Called during tunnel encapsulation to determine the
+ * source port for UDP-based tunnels (VXLAN, Geneve, etc.).
+ *
+ * If implemented, should return true and set 'src_port' to the desired
+ * source port value. If not implemented or if default behavior is
+ * desired, should return false to use the standard source port
+ * calculation. */
+ bool (*netdev_udp_tnl_get_src_port)(const struct dpif_offload *,
+ const struct netdev *ingress_netdev,
+ struct dp_packet *packet,
+ ovs_be16 *src_port);
+
/* Add or modify the specified flow directly in the offload datapath.
* The actual implementation may choose to handle the offload
* asynchronously by returning EINPROGRESS and invoking the supplied
@@ -1465,6 +1465,25 @@ dpif_offload_netdev_hw_post_process(struct netdev *netdev, unsigned pmd_id,
return rc;
}
+bool
+dpif_offload_netdev_udp_tnl_get_src_port(const struct netdev *ingress_netdev,
+ struct dp_packet *packet,
+ ovs_be16 *src_port)
+{
+ const struct dpif_offload *offload;
+
+ offload = ovsrcu_get(const struct dpif_offload *,
+ &ingress_netdev->dpif_offload);
+
+ if (OVS_UNLIKELY(!offload)
+ || !offload->class->netdev_udp_tnl_get_src_port) {
+ return false;
+ }
+
+ return offload->class->netdev_udp_tnl_get_src_port(offload, ingress_netdev,
+ packet, src_port);
+}
+
void
dpif_offload_datapath_register_flow_unreference_cb(
struct dpif *dpif, dpif_offload_flow_unreference_cb *cb)
@@ -114,6 +114,9 @@ bool dpif_offload_netdev_same_offload(const struct netdev *,
int dpif_offload_netdev_hw_post_process(struct netdev *, unsigned pmd_id,
struct dp_packet *,
void **flow_reference);
+bool dpif_offload_netdev_udp_tnl_get_src_port(const struct netdev *,
+ struct dp_packet *,
+ ovs_be16 *src_port);
/* Callback invoked when a hardware flow offload operation (put/del) completes.
@@ -36,6 +36,7 @@
#include "coverage.h"
#include "csum.h"
#include "dp-packet.h"
+#include "dpif-offload.h"
#include "netdev.h"
#include "netdev-vport.h"
#include "netdev-vport-private.h"
@@ -301,6 +302,7 @@ tnl_ol_pop(struct dp_packet *packet, int off)
void
netdev_tnl_push_udp_header(const struct netdev *netdev OVS_UNUSED,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data)
{
@@ -312,7 +314,10 @@ netdev_tnl_push_udp_header(const struct netdev *netdev OVS_UNUSED,
/* We may need to re-calculate the hash and this has to be done before
* modifying the packet. */
- udp_src = netdev_tnl_get_src_port(packet);
+ if (!ingress_netdev || !dpif_offload_netdev_udp_tnl_get_src_port(
+ ingress_netdev, packet, &udp_src)) {
+ udp_src = netdev_tnl_get_src_port(packet);
+ }
tnl_ol_push(packet, data);
udp = netdev_tnl_push_ip_header(packet, data->header, data->header_len,
@@ -532,6 +537,7 @@ err:
void
netdev_gre_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev OVS_UNUSED,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data)
{
@@ -695,6 +701,7 @@ err:
void
netdev_erspan_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev OVS_UNUSED,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data)
{
@@ -868,6 +875,7 @@ err:
void
netdev_gtpu_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev OVS_UNUSED,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data)
{
@@ -1001,6 +1009,7 @@ netdev_srv6_build_header(const struct netdev *netdev,
void
netdev_srv6_push_header(const struct netdev *netdev OVS_UNUSED,
+ const struct netdev *ingress_netdev OVS_UNUSED,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data)
{
@@ -35,6 +35,7 @@ netdev_gre_build_header(const struct netdev *netdev,
void
netdev_gre_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data);
struct dp_packet *
@@ -47,6 +48,7 @@ netdev_erspan_build_header(const struct netdev *netdev,
void
netdev_erspan_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data);
struct dp_packet *
@@ -57,6 +59,7 @@ netdev_gtpu_pop_header(struct dp_packet *packet);
void
netdev_gtpu_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data);
@@ -68,6 +71,7 @@ netdev_gtpu_build_header(const struct netdev *netdev,
struct dp_packet *netdev_srv6_pop_header(struct dp_packet *);
void netdev_srv6_push_header(const struct netdev *,
+ const struct netdev *ingress_netdev,
struct dp_packet *,
const struct ovs_action_push_tnl *);
@@ -77,6 +81,7 @@ int netdev_srv6_build_header(const struct netdev *,
void
netdev_tnl_push_udp_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data);
int
@@ -336,8 +336,13 @@ struct netdev_class {
/* build_header() can not build entire header for all packets for given
* flow. Push header is called for packet to build header specific to
* a packet on actual transmit. It uses partial header build by
- * build_header() which is passed as data. */
- void (*push_header)(const struct netdev *,
+ * build_header() which is passed as data.
+ *
+ * The 'ingress_netdev' points to the original ingress netdev for the
+ * 'packet'. This variable is valid only if hardware offload is enabled;
+ * otherwise, it will be NULL. */
+ void (*push_header)(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet *packet,
const struct ovs_action_push_tnl *data);
@@ -1003,6 +1003,7 @@ int netdev_build_header(const struct netdev *netdev,
* that netdev_has_tunnel_push_pop() returns true. */
int
netdev_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet_batch *batch,
const struct ovs_action_push_tnl *data)
{
@@ -1038,7 +1039,8 @@ netdev_push_header(const struct netdev *netdev,
}
dp_packet_ol_send_prepare(packet, 0);
}
- netdev->netdev_class->push_header(netdev, packet, data);
+ netdev->netdev_class->push_header(netdev, ingress_netdev, packet,
+ data);
pkt_metadata_init(&packet->md, data->out_port);
dp_packet_batch_refill(batch, packet, i);
@@ -259,6 +259,7 @@ int netdev_build_header(const struct netdev *, struct ovs_action_push_tnl *data,
const struct netdev_tnl_build_header_params *params);
int netdev_push_header(const struct netdev *netdev,
+ const struct netdev *ingress_netdev,
struct dp_packet_batch *,
const struct ovs_action_push_tnl *data);
void netdev_pop_header(struct netdev *netdev, struct dp_packet_batch *);
@@ -77,6 +77,17 @@ strip_metadata () {
]
m4_divert_pop([PREPARE_TESTS])
+m4_define([CHECK_FWD_PACKET],
+ [m4_if([$3], [], [], [AT_CHECK([ovs-vsctl set interface $1 options:$3=true])])
+ AT_CHECK([ovs-appctl netdev-dummy/receive $1 $(cat $4)])
+ m4_if([$5], [none], [], [
+ AT_CHECK([awk 1 $5 > expout]) dnl Also normalizes the line endings.
+ AT_CHECK([ovs-pcap $2.pcap > $2.pcap.txt 2>&1])
+ AT_CHECK([tail -n 1 $2.pcap.txt], [0], [expout])
+ ])
+ m4_if([$3], [], [], [AT_CHECK([ovs-vsctl remove interface $1 options $3])])
+])
+
AT_SETUP([dpif-netdev - netdev-dummy/receive])
# Create br0 with interfaces p0
OVS_VSWITCHD_START([add-port br0 p1 -- set interface p1 type=dummy ofport_request=1 -- ])
@@ -761,6 +772,85 @@ rx_offload_pipe_abort:1
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([dpif-netdev - full hw offload - tunnel entropy - dummy-pmd])
+OVS_VSWITCHD_START(
+ [add-br br1 -- set bridge br1 datapath-type=dummy -- \
+ add-port br1 p1 -- \
+ set Interface p1 type=dummy-pmd -- \
+ add-br br2 -- set bridge br2 datapath-type=dummy -- \
+ set bridge br2 other-config:hwaddr=aa:66:aa:66:00:00 -- \
+ add-port br2 p2 -- \
+ set Interface p2 type=dummy-pmd], [], [], [--dummy-numa 0])
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:hw-offload=true])
+OVS_WAIT_UNTIL([grep "Flow HW offload is enabled" ovs-vswitchd.log])
+
+AT_CHECK([ovs-vsctl add-port br1 t1 \
+ -- set Interface t1 type=vxlan \
+ options:remote_ip=1.1.2.92 options:key=123 \
+ options:csum=false ofport_request=11], [0])
+AT_CHECK([ovs-ofctl add-flow br1 in_port=p1,actions=mod_nw_dst:192.168.1.1,output:11])
+
+AT_CHECK([ovs-appctl netdev-dummy/ip4addr br2 1.1.2.88/24], [0], [OK
+])
+AT_CHECK([ovs-appctl tnl/neigh/set br2 1.1.2.92 aa:66:aa:66:00:01], [0], [OK
+])
+AT_CHECK([ovs-appctl tnl/egress_port_range 57363 57363], [0], [OK
+])
+AT_CHECK([ovs-appctl vlog/set dpif:file:dbg dpif_netdev:file:dbg \
+ dpif_offload_dummy:file:dbg netdev_dummy:file:dbg])
+
+AT_CHECK([ovs-vsctl set Interface p2 options:tx_pcap=p2.pcap])
+
+dnl ICMPv6.
+AT_DATA([ndp_frame], m4_join([],
+dnl p = Ether(src='04:bf:1b:d8:2d:2d', dst='33:33:ff:00:00:01')
+[3333ff00000104bf1bd82d2d86dd],
+dnl p /= IPv6(src='fe80::2', dst='ff02::1:ff00:1')
+[6000000000203afffe800000000000000000000000000002ff0200000000000000000001ff000001],
+dnl p /= ICMPv6ND_NS(type=135, tgt='fe80::1')
+[87006e2600000000fe800000000000000000000000000001],
+dnl p /= ICMPv6NDOptSrcLLAddr(lladdr='8a:bf:7e:2f:05:84')
+[01018abf7e2f0584]
+))
+
+AT_DATA([ndp_expected], m4_join([],
+dnl p = Ether(src='aa:66:aa:66:00:00', dst='aa:66:aa:66:00:01')
+[aa66aa660001aa66aa6600000800],
+dnl p /= IP(src='1.1.2.88', dst='1.1.2.92', id=0, flags='DF')
+[4500007a00004000401133be010102580101025c],
+dnl p /= UDP(sport=43938, chksum=0)
+[aba212b500660000],
+dnl p /= VXLAN(vni=123, flags='Instance')
+[0800000000007b00],
+dnl p /= Ether(src='04:bf:1b:d8:2d:2d', dst='33:33:ff:00:00:01')
+[3333ff00000104bf1bd82d2d86dd],
+dnl p /= IPv6(src='fe80::2', dst='ff02::1:ff00:1')
+[6000000000203afffe800000000000000000000000000002ff0200000000000000000001ff000001],
+dnl p /= ICMPv6ND_NS(type=135, tgt='fe80::1')
+[87006e2600000000fe800000000000000000000000000001],
+dnl p /= ICMPv6NDOptSrcLLAddr(lladdr='8a:bf:7e:2f:05:84')
+[01018abf7e2f0584]
+))
+
+# Sent two packets, second should be handled in hardware.
+CHECK_FWD_PACKET(p1, p2, , [ndp_frame], [ndp_expected])
+CHECK_FWD_PACKET(p1, p2, , [ndp_frame], [ndp_expected])
+
+# Check if we do not hit partial hw offload.
+AT_CHECK(
+ [ovs-appctl --format json dpif/offload/show \
+ | sed 's/.*"p1":{\([[^}]]*\)}.*/\1/; s/,/\n/g; s/"//g' \
+ | sed -n '/^rx_offload_/p' | sort], [0], [dnl
+rx_offload_full:1
+rx_offload_miss:1
+rx_offload_partial:0
+rx_offload_pipe_abort:0
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([dpif-netdev - check dpctl/add-flow in_port exact match])
OVS_VSWITCHD_START(
[add-port br0 p1 \
@@ -877,17 +967,6 @@ AT_CHECK([test `ovs-vsctl get Interface p2 statistics:tx_q0_packets` -gt 0 -a dn
OVS_VSWITCHD_STOP
AT_CLEANUP
-m4_define([CHECK_FWD_PACKET],
- [m4_if([$3], [], [], [AT_CHECK([ovs-vsctl set interface $1 options:$3=true])])
- AT_CHECK([ovs-appctl netdev-dummy/receive $1 $(cat $4)])
- m4_if([$5], [none], [], [
- AT_CHECK([awk 1 $5 > expout]) dnl Also normalizes the line endings.
- AT_CHECK([ovs-pcap $2.pcap > $2.pcap.txt 2>&1])
- AT_CHECK([tail -n 1 $2.pcap.txt], [0], [expout])
- ])
- m4_if([$3], [], [], [AT_CHECK([ovs-vsctl remove interface $1 options $3])])
-])
-
dnl CHECK_IP_CHECKSUMS rx_port tx_port good_pkt bad_pkt good_exp bad_exp
dnl
dnl Test combinations of Rx IP checksum flags for a good or bad packet
@@ -144,6 +144,7 @@ lldp
llvm
localnet
lockless
+lookups
loopback
malloc
mbps
This patch introduces a new API to the offload provider framework that allows hardware offload implementations to control UDP tunnel source port selection during tunnel encapsulation. Background and Motivation ========================== UDP-based tunnels (VXLAN, Geneve, etc.) use the UDP source port as an entropy field to enable ECMP load balancing across multiple paths in the network. The source port is typically calculated by hashing packet header fields (5-tuple or inner packet headers) to distribute flows across different paths. However, hardware offload implementations may require different approaches to source port calculation: 1. Hardware NICs may use different hash functions or hash inputs than the software datapath, which can lead to inconsistent flow distribution when mixing hardware and software paths. 2. Some hardware may support enhanced entropy mechanisms (e.g., using additional packet fields or hardware-specific hash engines) that provide better load distribution than the default software implementation. Design ====== This patch adds a new optional callback to the dpif_offload_class: bool (*netdev_udp_tnl_get_src_port)(const struct dpif_offload *, const struct netdev *ingress_netdev, struct dp_packet *packet, ovs_be16 *src_port); The callback is invoked during tunnel push operations when hardware offload is enabled and the original ingress port is known. It receives: - ingress_netdev: The original ingress port where the packet was received - packet: The inner packet to be encapsulated If the provider implements this callback and returns true, the returned src_port value is used. Otherwise, OVS falls back to the standard hash-based source port calculation. Acked-by: Eli Britstein <elibr@nvidia.com> Signed-off-by: Eelco Chaudron <echaudro@redhat.com> --- NOTE: This patch needs to be applied on top of the patch below: https://patchwork.ozlabs.org/project/openvswitch/list/?series=498322 Changes from v2 -> v3: - Fixed indentation - Added Eli's ACK - Rebased on depending patch. Changes from v1 -> v2: - Fixed order of include files. - Remove the const qualifier for the packet in the API. --- lib/dpif-netdev.c | 18 ++++- lib/dpif-offload-dummy.c | 133 +++++++++++++++++++++++++++++++++- lib/dpif-offload-provider.h | 13 ++++ lib/dpif-offload.c | 19 +++++ lib/dpif-offload.h | 3 + lib/netdev-native-tnl.c | 11 ++- lib/netdev-native-tnl.h | 5 ++ lib/netdev-provider.h | 9 ++- lib/netdev.c | 4 +- lib/netdev.h | 1 + tests/dpif-netdev.at | 101 +++++++++++++++++++++++--- utilities/checkpatch_dict.txt | 1 + 12 files changed, 298 insertions(+), 20 deletions(-)