diff mbox

[ovs-dev,v4,3/3] nsh: add dec_nsh_ttl action

Message ID 1503633771-112384-4-git-send-email-yi.y.yang@intel.com
State Changes Requested
Headers show

Commit Message

Yang, Yi Aug. 25, 2017, 4:02 a.m. UTC
IETF NSH spec defines a ttl field in NSH header, it is a 6-bit
field ranged from 0 to 63, it should be decremented by 1 every
hop, if it is 0 or it is so after decremented, the packet should
be dropped and a packet-in message is sent to main controller.

Signed-off-by: Yi Yang <yi.y.yang@intel.com>
---
 include/openvswitch/ofp-actions.h |  1 +
 lib/ofp-actions.c                 | 49 +++++++++++++++++++++++++++++++++++++++
 ofproto/ofproto-dpif-xlate.c      | 31 +++++++++++++++++++++++++
 tests/nsh.at                      | 23 +++++++++---------
 utilities/ovs-ofctl.8.in          | 13 ++++++++++-
 5 files changed, 105 insertions(+), 12 deletions(-)

Comments

Jan Scheurich Aug. 28, 2017, 12:47 p.m. UTC | #1
> -----Original Message-----
> From: Yi Yang [mailto:yi.y.yang@intel.com]
> 
> +static bool
> +compose_dec_nsh_ttl_action(struct xlate_ctx *ctx) {
> +    struct flow *flow = &ctx->xin->flow;
> +
> +    if ((flow->packet_type == htonl(PT_NSH)) ||
> +        (flow->dl_type == htons(ETH_TYPE_NSH))) {
> +        ctx->wc->masks.nsh.ttl = 0xff;
> +        if (flow->nsh.ttl > 1) {
> +            flow->nsh.ttl--;
> +            return false;
> +        } else {
> +            execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL,
> 0,
> +                                      NULL, 0);
> +        }
> +    }
> +
> +    /* Stop processing for current table. */
> +    xlate_report(ctx, OFT_WARN, "NSH decrement TTL exception");
> +    return true;

I see you followed the dec_mpls_ttl implementation here in that you terminate
translation and drop the packet when trying to decrement TTL of a non-NSH
packet. This is different from the dec_nw_ttl action, which does nothing when
executed on a non-IP packet. 

@Ben: What is the reason for different approaches in OVS?

@Yi: Why have you opted for the MPLS approach?

BR, Jan
Ben Pfaff Aug. 29, 2017, 3:40 p.m. UTC | #2
On Mon, Aug 28, 2017 at 12:47:52PM +0000, Jan Scheurich wrote:
> > -----Original Message-----
> > From: Yi Yang [mailto:yi.y.yang@intel.com]
> > 
> > +static bool
> > +compose_dec_nsh_ttl_action(struct xlate_ctx *ctx) {
> > +    struct flow *flow = &ctx->xin->flow;
> > +
> > +    if ((flow->packet_type == htonl(PT_NSH)) ||
> > +        (flow->dl_type == htons(ETH_TYPE_NSH))) {
> > +        ctx->wc->masks.nsh.ttl = 0xff;
> > +        if (flow->nsh.ttl > 1) {
> > +            flow->nsh.ttl--;
> > +            return false;
> > +        } else {
> > +            execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL,
> > 0,
> > +                                      NULL, 0);
> > +        }
> > +    }
> > +
> > +    /* Stop processing for current table. */
> > +    xlate_report(ctx, OFT_WARN, "NSH decrement TTL exception");
> > +    return true;
> 
> I see you followed the dec_mpls_ttl implementation here in that you terminate
> translation and drop the packet when trying to decrement TTL of a non-NSH
> packet. This is different from the dec_nw_ttl action, which does nothing when
> executed on a non-IP packet. 
> 
> @Ben: What is the reason for different approaches in OVS?
> 
> @Yi: Why have you opted for the MPLS approach?

I don't recall why these are different.

I don't think the difference is that significant in practice because,
outside of OpenFlow 1.0, OVS only allows an IP TTL decrement action in a
flow matches only IP packets, and similarly for MPLS TTL decrement.

I wouldn't object to making the behavior uniform.
diff mbox

Patch

diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index ad8e1be..1296a9c 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -93,6 +93,7 @@  struct vl_mff_map;
     OFPACT(DEC_MPLS_TTL,    ofpact_null,        ofpact, "dec_mpls_ttl") \
     OFPACT(PUSH_MPLS,       ofpact_push_mpls,   ofpact, "push_mpls")    \
     OFPACT(POP_MPLS,        ofpact_pop_mpls,    ofpact, "pop_mpls")     \
+    OFPACT(DEC_NSH_TTL,     ofpact_null,        ofpact, "dec_nsh_ttl")  \
                                                                         \
     /* Generic encap & decap */                                         \
     OFPACT(ENCAP,           ofpact_encap,       props, "encap")         \
diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c
index 71eb70c..1a92b95 100644
--- a/lib/ofp-actions.c
+++ b/lib/ofp-actions.c
@@ -348,6 +348,9 @@  enum ofp_raw_action_type {
     /* NX1.3+(47): struct nx_action_decap, ... */
     NXAST_RAW_DECAP,
 
+    /* NX1.3+(48): void. */
+    NXAST_RAW_DEC_NSH_TTL,
+
 /* ## ------------------ ## */
 /* ## Debugging actions. ## */
 /* ## ------------------ ## */
@@ -480,6 +483,7 @@  ofpact_next_flattened(const struct ofpact *ofpact)
     case OFPACT_NAT:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
         return ofpact_next(ofpact);
 
     case OFPACT_CLONE:
@@ -4330,6 +4334,39 @@  format_DECAP(const struct ofpact_decap *a,
     ds_put_format(s, "%s)%s", colors.paren, colors.end);
 }
 
+/* Action dec_nsh_ttl */
+
+static enum ofperr
+decode_NXAST_RAW_DEC_NSH_TTL(struct ofpbuf *out)
+{
+    ofpact_put_DEC_NSH_TTL(out);
+    return 0;
+}
+
+static void
+encode_DEC_NSH_TTL(const struct ofpact_null *null OVS_UNUSED,
+            enum ofp_version ofp_version OVS_UNUSED, struct ofpbuf *out)
+{
+    put_NXAST_DEC_NSH_TTL(out);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_DEC_NSH_TTL(char *arg OVS_UNUSED,
+           const struct ofputil_port_map *port_map OVS_UNUSED,
+           struct ofpbuf *ofpacts,
+           enum ofputil_protocol *usable_protocols OVS_UNUSED)
+{
+    ofpact_put_DEC_NSH_TTL(ofpacts);
+    return NULL;
+}
+
+static void
+format_DEC_NSH_TTL(const struct ofpact_null *a OVS_UNUSED,
+            const struct ofputil_port_map *port_map OVS_UNUSED, struct ds *s)
+{
+    ds_put_format(s, "%sdec_nsh_ttl%s", colors.special, colors.end);
+}
+
 
 /* Action structures for NXAST_RESUBMIT, NXAST_RESUBMIT_TABLE, and
  * NXAST_RESUBMIT_TABLE_CT.
@@ -7114,6 +7151,7 @@  ofpact_is_set_or_move_action(const struct ofpact *a)
     case OFPACT_SET_VLAN_VID:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
         return true;
     case OFPACT_BUNDLE:
     case OFPACT_CLEAR_ACTIONS:
@@ -7191,6 +7229,7 @@  ofpact_is_allowed_in_actions_set(const struct ofpact *a)
     case OFPACT_STRIP_VLAN:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
         return true;
 
     /* In general these actions are excluded because they are not part of
@@ -7304,6 +7343,7 @@  ofpacts_execute_action_set(struct ofpbuf *action_list,
     ofpacts_copy_last(action_list, action_set, OFPACT_PUSH_VLAN);
     ofpacts_copy_last(action_list, action_set, OFPACT_DEC_TTL);
     ofpacts_copy_last(action_list, action_set, OFPACT_DEC_MPLS_TTL);
+    ofpacts_copy_last(action_list, action_set, OFPACT_DEC_NSH_TTL);
     ofpacts_copy_all(action_list, action_set, ofpact_is_set_or_move_action);
     ofpacts_copy_last(action_list, action_set, OFPACT_SET_QUEUE);
 
@@ -7445,6 +7485,7 @@  ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
     case OFPACT_NAT:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
     default:
         return OVSINST_OFPIT11_APPLY_ACTIONS;
     }
@@ -8131,6 +8172,13 @@  ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
         }
         return 0;
 
+    case OFPACT_DEC_NSH_TTL:
+        if ((flow->packet_type != htonl(PT_NSH)) &&
+            (flow->dl_type != htons(ETH_TYPE_NSH))) {
+            inconsistent_match(usable_protocols);
+        }
+        return 0;
+
     default:
         OVS_NOT_REACHED();
     }
@@ -8626,6 +8674,7 @@  ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
     case OFPACT_NAT:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
     default:
         return false;
     }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index 1b99ad5..0f0a511 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -4832,6 +4832,28 @@  compose_dec_mpls_ttl_action(struct xlate_ctx *ctx)
     return true;
 }
 
+static bool
+compose_dec_nsh_ttl_action(struct xlate_ctx *ctx)
+{
+    struct flow *flow = &ctx->xin->flow;
+
+    if ((flow->packet_type == htonl(PT_NSH)) ||
+        (flow->dl_type == htons(ETH_TYPE_NSH))) {
+        ctx->wc->masks.nsh.ttl = 0xff;
+        if (flow->nsh.ttl > 1) {
+            flow->nsh.ttl--;
+            return false;
+        } else {
+            execute_controller_action(ctx, UINT16_MAX, OFPR_INVALID_TTL, 0,
+                                      NULL, 0);
+        }
+    }
+
+    /* Stop processing for current table. */
+    xlate_report(ctx, OFT_WARN, "NSH decrement TTL exception");
+    return true;
+}
+
 static void
 xlate_output_action(struct xlate_ctx *ctx,
                     ofp_port_t port, uint16_t max_len, bool may_packet_in)
@@ -5327,6 +5349,7 @@  reversible_actions(const struct ofpact *ofpacts, size_t ofpacts_len)
         case OFPACT_OUTPUT_TRUNC:
         case OFPACT_ENCAP:
         case OFPACT_DECAP:
+        case OFPACT_DEC_NSH_TTL:
             return false;
         }
     }
@@ -5537,6 +5560,7 @@  freeze_unroll_actions(const struct ofpact *a, const struct ofpact *end,
         case OFPACT_OUTPUT:
         case OFPACT_CONTROLLER:
         case OFPACT_DEC_MPLS_TTL:
+        case OFPACT_DEC_NSH_TTL:
         case OFPACT_DEC_TTL:
             /* These actions may generate asynchronous messages, which include
              * table ID and flow cookie information. */
@@ -6083,6 +6107,7 @@  recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
     case OFPACT_CLONE:
     case OFPACT_ENCAP:
     case OFPACT_DECAP:
+    case OFPACT_DEC_NSH_TTL:
     case OFPACT_UNROLL_XLATE:
     case OFPACT_CT:
     case OFPACT_CT_CLEAR:
@@ -6405,6 +6430,12 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             }
             break;
 
+        case OFPACT_DEC_NSH_TTL:
+            if (compose_dec_nsh_ttl_action(ctx)) {
+                return;
+            }
+            break;
+
         case OFPACT_DEC_TTL:
             wc->masks.nw_ttl = 0xff;
             if (compose_dec_ttl(ctx, ofpact_get_DEC_TTL(a))) {
diff --git a/tests/nsh.at b/tests/nsh.at
index 93d8b42..521365b 100644
--- a/tests/nsh.at
+++ b/tests/nsh.at
@@ -13,7 +13,7 @@  OVS_VSWITCHD_START([dnl
     add-port br0 p2 -- set Interface p2 type=dummy ofport_request=2])
 
 AT_DATA([flows.txt], [dnl
-    table=0,in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,actions=set_field:0x2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,2
+    table=0,in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344,actions=set_field:0x2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,dec_nsh_ttl,2
 ])
 
 AT_CHECK([
@@ -21,7 +21,7 @@  AT_CHECK([
     ovs-ofctl -Oopenflow13 add-flows br0 flows.txt
     ovs-ofctl -Oopenflow13 dump-flows br0 | ofctl_strip | sort | grep actions
 ], [0], [dnl
- in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344 actions=set_field:2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,output:2
+ in_port=1,dl_type=0x894f,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344 actions=set_field:2->nsh_flags,set_field:254->nsh_si,set_field:0x44332211->nsh_c1,dec_nsh_ttl,output:2
 ])
 
 AT_CHECK([
@@ -35,11 +35,12 @@  bridge("br0")
     set_field:2->nsh_flags
     set_field:254->nsh_si
     set_field:0x44332211->nsh_c1
+    dec_nsh_ttl
     output:2
 
-Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=2,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=254,nsh_c1=0x44332211,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
+Final flow: in_port=1,vlan_tci=0x0000,dl_src=00:00:00:00:00:00,dl_dst=00:00:00:00:00:00,dl_type=0x894f,nsh_flags=2,nsh_ttl=62,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=254,nsh_c1=0x44332211,nsh_c2=0x55667788,nsh_c3=0x99aabbcc,nsh_c4=0xddeeff00,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
 Megaflow: recirc_id=0,eth,in_port=1,dl_type=0x894f,nsh_flags=0,nsh_ttl=63,nsh_mdtype=1,nsh_np=3,nsh_spi=0x123456,nsh_si=255,nsh_c1=0x11223344
-Datapath actions: set(nsh(flags=2,ttl=63,spi=0x123456,si=254,c1=0x44332211)),2
+Datapath actions: set(nsh(flags=2,ttl=62,spi=0x123456,si=254,c1=0x44332211)),2
 ])
 
 OVS_VSWITCHD_STOP
@@ -539,8 +540,8 @@  AT_DATA([br-in2.txt], [dnl
     table=2,packet_type=(1,0x894f),nsh_spi=0x3020,nsh_si=254,actions=output:2030
     table=2,packet_type=(1,0x894f),nsh_spi=0x1020,nsh_si=255,actions=encap(ethernet),set_field:77:88:99:aa:bb:cc->dl_dst,goto_table:4
     table=2,packet_type=(1,0x894f),nsh_spi=0x1020,nsh_si=254,actions=output:2010
-    table=4,dl_type=0x894f,dl_dst=11:22:33:44:55:66,actions=set_field:254->nsh_si,decap(),resubmit(,2)
-    table=4,dl_type=0x894f,dl_dst=77:88:99:aa:bb:cc,actions=set_field:254->nsh_si,decap(),resubmit(,2)
+    table=4,dl_type=0x894f,dl_dst=11:22:33:44:55:66,actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
+    table=4,dl_type=0x894f,dl_dst=77:88:99:aa:bb:cc,actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
 ])
 
 # br-in3 is SFC classifier (table 1) and final SFF (tables 2,3)
@@ -603,8 +604,8 @@  AT_CHECK([
  table=2, packet_type=(1,0x894f),nsh_spi=0x1020,nsh_si=255 actions=encap(ethernet),set_field:77:88:99:aa:bb:cc->eth_dst,goto_table:4
  table=2, packet_type=(1,0x894f),nsh_spi=0x3020,nsh_si=254 actions=output:2030
  table=2, packet_type=(1,0x894f),nsh_spi=0x3020,nsh_si=255 actions=encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,goto_table:4
- table=4, dl_dst=11:22:33:44:55:66,dl_type=0x894f actions=set_field:254->nsh_si,decap(),resubmit(,2)
- table=4, dl_dst=77:88:99:aa:bb:cc,dl_type=0x894f actions=set_field:254->nsh_si,decap(),resubmit(,2)
+ table=4, dl_dst=11:22:33:44:55:66,dl_type=0x894f actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
+ table=4, dl_dst=77:88:99:aa:bb:cc,dl_type=0x894f actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
  ip,in_port=30 actions=decap(),goto_table:1
  n_packets=2, n_bytes=216, packet_type=(1,0x894f),in_port=3010 actions=goto_table:2
  packet_type=(1,0x800),in_port=30 actions=goto_table:1
@@ -632,7 +633,7 @@  AT_CHECK([
     ovs-appctl dpctl/dump-flows dummy@ovs-dummy | strip_used | grep -v ipv6 | sort
 ], [0], [flow-dump from non-dpdk interfaces:
 recirc_id(0),in_port(4),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=192.168.10.20/255.255.255.248,frag=no), packets:1, bytes:98, used:0.0s, actions:pop_eth,encap_nsh(flags=0,ttl=63,mdtype=1,np=1,spi=0x3020,si=255,c1=0x0,c2=0x0,c3=0x0,c4=0x0),clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:02,src=aa:55:00:00:00:01,dl_type=0x0800),ipv4(src=10.0.0.1,dst=10.0.0.2,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(1)),set(ipv4(src=20.0.0.1,dst=20.0.0.2)),tnl_pop(4789))
-tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(spi=0x3020,si=255), packets:1, bytes:108, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(spi=0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4(src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(src=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))
+tunnel(tun_id=0x0,src=20.0.0.1,dst=20.0.0.2,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(ttl=63,spi=0x3020,si=255), packets:1, bytes:108, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=11:22:33:44:55:66),set(nsh(ttl=62,spi=0x3020,si=254)),pop_eth,clone(tnl_push(tnl_port(4789),header(size=50,type=4,eth(dst=aa:55:00:00:00:03,src=aa:55:00:00:00:02,dl_type=0x0800),ipv4(src=20.0.0.2,dst=20.0.0.3,proto=17,tos=0,ttl=64,frag=0x4000),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0xc000004,vni=0x0)),out_port(2)),set(ipv4(src=30.0.0.2,dst=30.0.0.3)),tnl_pop(4789))
 tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0),in_port(4789),packet_type(ns=1,id=0x894f),nsh(np=1,spi=0x3020,si=254), packets:1, bytes:108, used:0.0s, actions:decap_nsh(),recirc(0x2)
 tunnel(tun_id=0x0,src=30.0.0.2,dst=30.0.0.3,flags(-df-csum+key)),recirc_id(0x2),in_port(4789),packet_type(ns=1,id=0x800),ipv4(frag=no), packets:1, bytes:84, used:0.0s, actions:push_eth(src=00:00:00:00:00:00,dst=aa:55:aa:55:00:03),6
 ])
@@ -658,8 +659,8 @@  AT_CHECK([
  table=2, n_packets=2, n_bytes=216, packet_type=(1,0x894f),nsh_spi=0x3020,nsh_si=255 actions=encap(ethernet),set_field:11:22:33:44:55:66->eth_dst,goto_table:4
  table=2, packet_type=(1,0x894f),nsh_spi=0x1020,nsh_si=254 actions=output:2010
  table=2, packet_type=(1,0x894f),nsh_spi=0x1020,nsh_si=255 actions=encap(ethernet),set_field:77:88:99:aa:bb:cc->eth_dst,goto_table:4
- table=4, dl_dst=77:88:99:aa:bb:cc,dl_type=0x894f actions=set_field:254->nsh_si,decap(),resubmit(,2)
- table=4, n_packets=2, n_bytes=216, dl_dst=11:22:33:44:55:66,dl_type=0x894f actions=set_field:254->nsh_si,decap(),resubmit(,2)
+ table=4, dl_dst=77:88:99:aa:bb:cc,dl_type=0x894f actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
+ table=4, n_packets=2, n_bytes=216, dl_dst=11:22:33:44:55:66,dl_type=0x894f actions=set_field:254->nsh_si,dec_nsh_ttl,decap(),resubmit(,2)
  ip,in_port=30 actions=decap(),goto_table:1
  n_packets=2, n_bytes=216, packet_type=(1,0x894f),in_port=3010 actions=goto_table:2
  n_packets=2, n_bytes=216, packet_type=(1,0x894f),in_port=3020 actions=goto_table:2
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index c65de97..9b3e72d 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -1280,6 +1280,15 @@  Processing the current set of actions then stops.  However, if the current
 set of actions was reached through ``resubmit'' then remaining actions in
 outer levels resume processing.
 .
+.IP \fBdec_nsh_ttl\fR
+Decrement TTL of the outer NSH header of a packet.  If the TTL
+is initially zero or decrementing would make it so, no decrement occurs.
+Instead, a ``packet-in'' message with reason code \fBOFPR_INVALID_TTL\fR
+is sent to the main controller (id zero), if it has enabled receiving them.
+Processing the current set of actions then stops.  However, if the current
+set of actions was reached through ``resubmit'' then remaining actions in
+outer levels resume processing.
+.
 .IP \fBnote:\fR[\fIhh\fR]...
 Does nothing at all.  Any number of bytes represented as hex digits
 \fIhh\fR may be included.  Pairs of hex digits may be separated by
@@ -1578,6 +1587,8 @@  the action set, the one written later replaces the earlier action:
 \fBdec_ttl\fR
 .IQ
 \fBdec_mpls_ttl\fR
+.IQ
+\fBdec_nsh_ttl\fR
 .
 .IP 7.
 \fBload\fR
@@ -1638,7 +1649,7 @@  not visible.)
 .RE
 .IP
 Only the actions listed above may be written to the action set.
-\fBencap\fR and \fBdecap\fR actions are nonstandard.
+\fBencap\fR, \fBdecap\fR and \fBdec_nsh_ttl\fR actions are nonstandard.
 .
 .IP \fBwrite_metadata\fB:\fIvalue\fR[/\fImask\fR]
 Updates the metadata field for the flow. If \fImask\fR is omitted, the