diff --git a/Documentation/howto/dpdk.rst b/Documentation/howto/dpdk.rst
index be950d7ce..c40fcafcb 100644
--- a/Documentation/howto/dpdk.rst
+++ b/Documentation/howto/dpdk.rst
@@ -395,6 +395,7 @@ Supported actions for hardware offload are:
 - Modification of Ethernet (mod_dl_src/mod_dl_dst).
 - Modification of IPv4 (mod_nw_src/mod_nw_dst/mod_nw_ttl).
 - Modification of TCP/UDP (mod_tp_src/mod_tp_dst).
+- VLAN Push/Pop (push_vlan/pop_vlan).
 
 Further Reading
 ---------------
diff --git a/NEWS b/NEWS
index 3dbd8ec0e..c1311e366 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ Post-v2.13.0
    - DPDK:
      * Deprecated DPDK pdump packet capture support removed.
      * Deprecated DPDK ring ports (dpdkr) are no longer supported.
+     * Add hardware offload support for VLAN Push/Pop actions (experimental).
    - Linux datapath:
      * Support for kernel versions up to 5.5.x.
    - AF_XDP:
diff --git a/lib/netdev-offload-dpdk.c b/lib/netdev-offload-dpdk.c
index f8c46bbaa..c57586a48 100644
--- a/lib/netdev-offload-dpdk.c
+++ b/lib/netdev-offload-dpdk.c
@@ -420,6 +420,36 @@ dump_flow_action(struct ds *s, const struct rte_flow_action *actions)
         } else {
             ds_put_format(s, "  Set-%s-tcp/udp-port = null\n", dirstr);
         }
+    } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN) {
+        const struct rte_flow_action_of_push_vlan *rte_push_vlan =
+            actions->conf;
+        ds_put_cstr(s, "rte flow push-vlan action:\n");
+        if (rte_push_vlan) {
+            ds_put_format(s, "  Push-vlan: 0x%"PRIx16"\n",
+                          ntohs(rte_push_vlan->ethertype));
+        } else {
+            ds_put_format(s, "  Push-vlan = null\n");
+        }
+    } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP) {
+        struct rte_flow_action_of_set_vlan_pcp *rte_vlan_pcp = actions->conf;
+        ds_put_cstr(s, "rte flow set-vlan-pcp action:\n");
+        if (rte_vlan_pcp) {
+            ds_put_format(s, "  Set-vlan-pcp: %"PRIu8"\n",
+                          rte_vlan_pcp->vlan_pcp);
+        } else {
+            ds_put_format(s, "  Set-vlan-pcp = null\n");
+        }
+    } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID) {
+        struct rte_flow_action_of_set_vlan_vid *rte_vlan_vid = actions->conf;
+        ds_put_cstr(s, "rte flow set-vlan-vid action:\n");
+        if (rte_vlan_vid) {
+            ds_put_format(s, "  Set-vlan-vid: %"PRIu16"\n",
+                          ntohs(rte_vlan_vid->vlan_vid));
+        } else {
+            ds_put_format(s, "  Set-vlan-vid = null\n");
+        }
+    } else if (actions->type == RTE_FLOW_ACTION_TYPE_OF_POP_VLAN) {
+        ds_put_cstr(s, "rte flow pop-vlan action\n");
     } else {
         ds_put_format(s, "unknown rte flow action (%d)\n", actions->type);
     }
@@ -970,6 +1000,33 @@ parse_set_actions(struct flow_actions *actions,
     return 0;
 }
 
+static int
+parse_vlan_push_action(struct flow_actions *actions,
+                       const struct ovs_action_push_vlan *vlan_push)
+{
+    struct rte_flow_action_of_push_vlan *rte_push_vlan;
+    struct rte_flow_action_of_set_vlan_pcp *rte_vlan_pcp;
+    struct rte_flow_action_of_set_vlan_vid *rte_vlan_vid;
+
+    rte_push_vlan = xzalloc(sizeof *rte_push_vlan);
+    rte_push_vlan->ethertype = vlan_push->vlan_tpid;
+    add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_PUSH_VLAN,
+                    rte_push_vlan);
+
+    rte_vlan_pcp = xzalloc(sizeof *rte_vlan_pcp);
+    rte_vlan_pcp->vlan_pcp = vlan_tci_to_pcp(vlan_push->vlan_tci);
+    add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_PCP,
+                    rte_vlan_pcp);
+
+    rte_vlan_vid = xzalloc(sizeof *rte_vlan_vid);
+    rte_vlan_vid->vlan_vid =
+        rte_cpu_to_be_16(vlan_tci_to_vid(vlan_push->vlan_tci));
+    add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_SET_VLAN_VID,
+                    rte_vlan_vid);
+
+    return 0;
+}
+
 static int
 parse_flow_actions(struct netdev *netdev,
                    struct flow_actions *actions,
@@ -998,6 +1055,13 @@ parse_flow_actions(struct netdev *netdev,
                                   masked)) {
                 return -1;
             }
+        } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_PUSH_VLAN) {
+            const struct ovs_action_push_vlan *vlan = nl_attr_get(nla);
+            if (parse_vlan_push_action(actions, vlan)) {
+                return -1;
+            }
+        } else if (nl_attr_type(nla) == OVS_ACTION_ATTR_POP_VLAN) {
+            add_flow_action(actions, RTE_FLOW_ACTION_TYPE_OF_POP_VLAN, NULL);
         } else {
             VLOG_DBG_RL(&rl, "Unsupported action type %d", nl_attr_type(nla));
             return -1;
