diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 64531a02c0..9384edcd96 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -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;
     }
diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c
index 4335b199b9..11b339719d 100644
--- a/lib/dpif-offload-dummy.c
+++ b/lib/dpif-offload-dummy.c
@@ -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,                              \
diff --git a/lib/dpif-offload-provider.h b/lib/dpif-offload-provider.h
index 259de2c299..36d07190db 100644
--- a/lib/dpif-offload-provider.h
+++ b/lib/dpif-offload-provider.h
@@ -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
diff --git a/lib/dpif-offload.c b/lib/dpif-offload.c
index cbf1f6c704..d004cb1089 100644
--- a/lib/dpif-offload.c
+++ b/lib/dpif-offload.c
@@ -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)
diff --git a/lib/dpif-offload.h b/lib/dpif-offload.h
index 0f66d8cd8e..1b778168e6 100644
--- a/lib/dpif-offload.h
+++ b/lib/dpif-offload.h
@@ -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.
diff --git a/lib/netdev-native-tnl.c b/lib/netdev-native-tnl.c
index 008b452b8a..f27b8b6926 100644
--- a/lib/netdev-native-tnl.c
+++ b/lib/netdev-native-tnl.c
@@ -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)
 {
diff --git a/lib/netdev-native-tnl.h b/lib/netdev-native-tnl.h
index 47d6b6bbcf..ec1531d550 100644
--- a/lib/netdev-native-tnl.h
+++ b/lib/netdev-native-tnl.h
@@ -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
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index 136d8188c2..9aedb101fb 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -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);
 
diff --git a/lib/netdev.c b/lib/netdev.c
index daa4287362..2f08468c21 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -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);
diff --git a/lib/netdev.h b/lib/netdev.h
index 40f1621eca..8aab8bc062 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -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 *);
diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
index 0480730cab..5b6c961f90 100644
--- a/tests/dpif-netdev.at
+++ b/tests/dpif-netdev.at
@@ -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
diff --git a/utilities/checkpatch_dict.txt b/utilities/checkpatch_dict.txt
index c9b758d63c..a923cb659c 100644
--- a/utilities/checkpatch_dict.txt
+++ b/utilities/checkpatch_dict.txt
@@ -144,6 +144,7 @@ lldp
 llvm
 localnet
 lockless
+lookups
 loopback
 malloc
 mbps
