diff --git a/lib/odp-util.c b/lib/odp-util.c
index dbd4554d0626..2ec889c417e5 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -715,6 +715,24 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data)
         }
 
         ds_put_char(ds, ')');
+    } else if (data->tnl_type == OVS_VPORT_TYPE_SRV6) {
+        const struct srv6_base_hdr *srh;
+        struct in6_addr *segs;
+        int nr_segs;
+        int i;
+
+        srh = (const struct srv6_base_hdr *) l4;
+        segs = ALIGNED_CAST(struct in6_addr *, srh + 1);
+        nr_segs = srh->last_entry + 1;
+
+        ds_put_format(ds, "srv6(");
+        ds_put_format(ds, "segments_left=%d", srh->rt_hdr.segments_left);
+        ds_put_format(ds, ",segs(");
+        for (i = 0; i < nr_segs; i++) {
+            ds_put_format(ds, i > 0 ? "," : "");
+            ipv6_format_addr(&segs[nr_segs - i - 1], ds);
+        }
+        ds_put_format(ds, "))");
     } else if (data->tnl_type == OVS_VPORT_TYPE_GRE ||
                data->tnl_type == OVS_VPORT_TYPE_IP6GRE) {
         const struct gre_base_hdr *greh;
@@ -1534,6 +1552,7 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
     uint8_t hwid, dir;
     uint32_t teid;
     uint8_t gtpu_flags, gtpu_msgtype;
+    uint8_t segments_left;
 
     if (!ovs_scan_len(s, &n, "tnl_push(tnl_port(%"SCNi32"),", &data->tnl_port)) {
         return -EINVAL;
@@ -1775,6 +1794,57 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
         tnl_type = OVS_VPORT_TYPE_GTPU;
         header_len = sizeof *eth + ip_len +
                      sizeof *udp + sizeof *gtph;
+    } else if (ovs_scan_len(s, &n, "srv6(segments_left=%"SCNu8,
+                            &segments_left)) {
+        struct srv6_base_hdr *srh = (struct srv6_base_hdr *) (ip6 + 1);
+        char seg_s[IPV6_SCAN_LEN + 1];
+        struct in6_addr *segs;
+        struct in6_addr seg;
+        uint8_t n_segs = 0;
+
+        if (segments_left + 1 > SRV6_MAX_SEGS) {
+            return -EINVAL;
+        }
+
+        ip6->ip6_nxt = IPPROTO_ROUTING;
+
+        srh->rt_hdr.hdrlen = 2 * (segments_left + 1);
+        srh->rt_hdr.segments_left = segments_left;
+        srh->rt_hdr.type = IPV6_SRCRT_TYPE_4;
+        srh->last_entry = segments_left;
+
+        tnl_type = OVS_VPORT_TYPE_SRV6;
+        header_len = sizeof *eth + ip_len +
+                     sizeof *srh + 8 * srh->rt_hdr.hdrlen;
+        /* Parse segment list. */
+        if (!ovs_scan_len(s, &n, ",segs(")) {
+            return -EINVAL;
+        }
+
+        segs = ALIGNED_CAST(struct in6_addr *, srh + 1);
+        segs += segments_left;
+
+        while (ovs_scan_len(s, &n, IPV6_SCAN_FMT, seg_s)
+               && inet_pton(AF_INET6, seg_s, &seg) == 1) {
+            if (n_segs == segments_left + 1) {
+                return -EINVAL;
+            }
+
+            memcpy(segs--, &seg, sizeof *segs);
+            n_segs++;
+
+            if (s[n] == ',') {
+                n++;
+            }
+        }
+
+        if (!ovs_scan_len(s, &n, ")))")) {
+            return -EINVAL;
+        }
+
+        if (n_segs != segments_left + 1) {
+            return -EINVAL;
+        }
     } else {
         return -EINVAL;
     }
diff --git a/python/ovs/flow/odp.py b/python/ovs/flow/odp.py
index db63afc8d64d..88aee17fb2a4 100644
--- a/python/ovs/flow/odp.py
+++ b/python/ovs/flow/odp.py
@@ -474,6 +474,14 @@ class ODPFlow(Flow):
                                             }
                                         )
                                     ),
+                                    "srv6": nested_kv_decoder(
+                                        KVDecoders(
+                                            {
+                                                "segments_left": decode_int,
+                                                "segs": decode_default,
+                                            }
+                                        )
+                                    ),
                                 }
                             )
                         ),
diff --git a/python/ovs/tests/test_odp.py b/python/ovs/tests/test_odp.py
index f8017ca8a169..a50d3185cc62 100644
--- a/python/ovs/tests/test_odp.py
+++ b/python/ovs/tests/test_odp.py
@@ -452,6 +452,22 @@ def test_odp_fields(input_string, expected):
                 ),
             ],
         ),
+        (
+            "actions:tnl_push(header(srv6(segments_left=1,segs(2001:cafe::90,2001:cafe::91))))",  # noqa: E501
+            [
+                KeyValue(
+                    "tnl_push",
+                    {
+                        "header": {
+                            "srv6": {
+                                "segments_left": 1,
+                                "segs": "2001:cafe::90,2001:cafe::91",
+                            }
+                        }
+                    },
+                ),
+            ],
+        ),
         (
             "actions:clone(1),clone(clone(push_vlan(vid=12,pcp=0),2),1)",
             [
diff --git a/tests/odp.at b/tests/odp.at
index 26cda2967239..ba20604e43da 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -342,6 +342,8 @@ tnl_push(tnl_port(6),header(size=70,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1
 tnl_push(tnl_port(6),header(size=70,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1))
 tnl_push(tnl_port(6),header(size=78,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1))
 tnl_push(tnl_port(6),header(size=70,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=17,tclass=0x0,hlimit=64),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1))
+tnl_push(tnl_port(6),header(size=78,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=0,segs(2001:cafe::90))),out_port(1))
+tnl_push(tnl_port(6),header(size=110,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=2,segs(2001:cafe::90,2001:cafe::91,2001:cafe::92))),out_port(1))
 ct
 ct(commit)
 ct(commit,zone=5)
@@ -400,8 +402,14 @@ AT_CLEANUP
 
 AT_SETUP([OVS datapath actions parsing and formatting - invalid forms])
 dnl This caused a hang in older versions.
-AT_CHECK([echo 'encap_nsh@:{@' | ovstest test-odp parse-actions
-], [0], [dnl
+AT_DATA([actions.txt], [dnl
+encap_nsh@:{@
+tnl_push(tnl_port(6),header(size=94,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=2,segs(2001:cafe::90,2001:cafe::91))),out_port(1))
+tnl_push(tnl_port(6),header(size=126,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=2,segs(2001:cafe::90,2001:cafe::91,2001:cafe::92,2001:cafe::93))),out_port(1))
+])
+AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], [dnl
+odp_actions_from_string: error
+odp_actions_from_string: error
 odp_actions_from_string: error
 ])
 AT_CLEANUP
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 2cf306c67ecf..e300fe3a0d26 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -202,6 +202,8 @@ AT_CHECK([ovs-vsctl add-port int-br t2 -- set Interface t2 type=vxlan \
                        options:remote_ip=flow options:key=123 ofport_request=5\
                     -- add-port int-br t5 -- set Interface t5 type=gre \
                        options:remote_ip=2001:cafe::92 options:key=455 options:packet_type=legacy_l3 ofport_request=6\
+                    -- add-port int-br t6 -- set Interface t6 type=srv6 \
+                       options:remote_ip=2001:cafe::92 ofport_request=7\
                        ], [0])
 
 AT_CHECK([ovs-appctl dpif/show], [0], [dnl
@@ -216,12 +218,15 @@ dummy@ovs-dummy: hit:0 missed:0
     t3 4/4789: (vxlan: csum=true, out_key=flow, remote_ip=2001:cafe::93)
     t4 5/6081: (geneve: key=123, remote_ip=flow)
     t5 6/3: (gre: key=455, packet_type=legacy_l3, remote_ip=2001:cafe::92)
+    t6 7/6: (srv6: remote_ip=2001:cafe::92)
 ])
 
 AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
 Listening ports:
 genev_sys_6081 (6081) ref_cnt=1
 gre_sys (3) ref_cnt=2
+srv6_sys (6) ref_cnt=1
+srv6_sys (6) ref_cnt=1
 vxlan_sys_4789 (4789) ref_cnt=2
 ])
 
@@ -363,6 +368,8 @@ AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
 Listening ports:
 genev_sys_6081 (6081) ref_cnt=1
 gre_sys (3) ref_cnt=2
+srv6_sys (6) ref_cnt=1
+srv6_sys (6) ref_cnt=1
 vxlan_sys_4789 (4789) ref_cnt=2
 ])
 
@@ -384,6 +391,12 @@ AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: tnl_pop(6081)
 ])
 
+dnl Check SRv6 tunnel pop
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=4,tclass=0x0,hlimit=64)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: tnl_pop(6)
+])
+
 dnl Check VXLAN tunnel push
 AT_CHECK([ovs-ofctl add-flow int-br action=2])
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
@@ -405,6 +418,13 @@ AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: tnl_push(tnl_port(3),header(size=62,type=109,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100)),1
 ])
 
+dnl Check SRv6 tunnel push
+AT_CHECK([ovs-ofctl add-flow int-br action=7])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: pop_eth,tnl_push(tnl_port(6),header(size=78,type=112,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=0,segs(2001:cafe::92))),out_port(100)),1
+])
+
 dnl Check Geneve tunnel push
 AT_CHECK([ovs-ofctl add-flow int-br "actions=set_field:2001:cafe::92->tun_ipv6_dst,5"])
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:01),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
@@ -510,6 +530,8 @@ AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
 Listening ports:
 genev_sys_6081 (6081) ref_cnt=1
 gre_sys (3) ref_cnt=1
+srv6_sys (6) ref_cnt=1
+srv6_sys (6) ref_cnt=1
 vxlan_sys_4789 (4789) ref_cnt=1
 vxlan_sys_4790 (4790) ref_cnt=1
 ])
@@ -518,6 +540,7 @@ AT_CHECK([ovs-vsctl del-port int-br t1 \
                     -- del-port int-br t2 \
                     -- del-port int-br t4 \
                     -- del-port int-br t5 \
+                    -- del-port int-br t6 \
                        ], [0])
 
 dnl Check tunnel lookup entries after deleting all remaining tunnel ports
