[ovs-dev] [#8024v2 4/5] Support matching and modifying IP TTL.

Justin Pettit jpettit at nicira.com
Tue Nov 8 23:57:31 UTC 2011


Add support matching the IPv4 TTL and IPv6 hop limit fields.  This
commit also adds support for modifying the IPv4 TTL.  Modifying the IPv6
hop limit isn't currently supported, since we don't support modifying
IPv6 headers.

We will likely want to change the user-space interface, since basic
matching and setting the TTL are not generally useful.  We will probably
want the ability to match on extraordinary events (such as TTL of 0 or 1)
and a decrement action.

Feature #8024

Signed-off-by: Justin Pettit <jpettit at nicira.com>
---
 NEWS                          |    2 ++
 datapath/actions.c            |    9 +++++++++
 datapath/flow.c               |    6 ++++++
 datapath/flow.h               |    3 ++-
 include/linux/openvswitch.h   |    2 ++
 include/openflow/nicira-ext.h |    9 +++++++++
 lib/classifier.c              |   15 +++++++++++++--
 lib/classifier.h              |    1 +
 lib/flow.c                    |   24 ++++++++++++++++--------
 lib/flow.h                    |   18 ++++++++++--------
 lib/meta-flow.c               |   25 +++++++++++++++++++++++++
 lib/meta-flow.h               |    1 +
 lib/nx-match.c                |   17 ++++++++++++++++-
 lib/nx-match.def              |    1 +
 lib/nx-match.h                |    3 ++-
 lib/odp-util.c                |   24 +++++++++++++++++-------
 lib/ofp-parse.c               |    1 +
 lib/ofp-util.c                |   16 +++++++++++-----
 ofproto/ofproto-dpif.c        |    2 ++
 tests/odp.at                  |   36 ++++++++++++++++++------------------
 tests/ofp-print.at            |    2 +-
 tests/ofproto-dpif.at         |   16 ++++++++--------
 tests/ovs-ofctl.at            |   10 ++++++++++
 utilities/ovs-ofctl.8.in      |    9 +++++++++
 24 files changed, 192 insertions(+), 60 deletions(-)

diff --git a/NEWS b/NEWS
index d13c906..249ffac 100644
--- a/NEWS
+++ b/NEWS
@@ -3,7 +3,9 @@ post-v1.3.0
     - OpenFlow:
        - Added ability to match on IPv6 flow label through NXM.
        - Added ability to match on ECN bits in IPv4 and IPv6 through NXM.
+       - Added ability to match on TTL in IPv4 and IPv6 through NXM.
        - Added ability to modify ECN bits in IPv4 and IPv6.
+       - Added ability to modify TTL in IPv4.
     - ovs-appctl:
       - New "fdb/flush" command to flush bridge's MAC learning table.
 
diff --git a/datapath/actions.c b/datapath/actions.c
index e8b0ce3..b138cb7 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -156,6 +156,12 @@ static void set_ip_tos(struct sk_buff *skb, struct iphdr *nh, u8 new_tos)
 	nh->tos = new_tos;
 }
 
+static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl)
+{
+	csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8));
+	nh->ttl = new_ttl;
+}
+
 static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key)
 {
 	struct iphdr *nh;
@@ -177,6 +183,9 @@ static int set_ipv4(struct sk_buff *skb, const struct ovs_key_ipv4 *ipv4_key)
 	if (ipv4_key->ipv4_tos != nh->tos)
 		set_ip_tos(skb, nh, ipv4_key->ipv4_tos);
 
+	if (ipv4_key->ipv4_ttl != nh->ttl)
+		set_ip_ttl(skb, nh, ipv4_key->ipv4_ttl);
+
 	return 0;
 }
 
diff --git a/datapath/flow.c b/datapath/flow.c
index f3e3751..a27db86 100644
--- a/datapath/flow.c
+++ b/datapath/flow.c
@@ -200,6 +200,7 @@ static int parse_ipv6hdr(struct sk_buff *skb, struct sw_flow_key *key,
 
 	key->ip.proto = NEXTHDR_NONE;
 	key->ip.tos = ipv6_get_dsfield(nh);
+	key->ip.ttl = nh->hop_limit;
 	key->ipv6.label = *(u32 *)nh & htonl(IPV6_FLOWINFO_FLOWLABEL);
 	ipv6_addr_copy(&key->ipv6.addr.src, &nh->saddr);
 	ipv6_addr_copy(&key->ipv6.addr.dst, &nh->daddr);
@@ -689,6 +690,7 @@ int flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key,
 
 		key->ip.proto = nh->protocol;
 		key->ip.tos = nh->tos;
+		key->ip.ttl = nh->ttl;
 
 		offset = nh->frag_off & htons(IP_OFFSET);
 		if (offset) {
@@ -961,6 +963,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
 				goto invalid;
 			swkey->ip.proto = ipv4_key->ipv4_proto;
 			swkey->ip.tos = ipv4_key->ipv4_tos;
+			swkey->ip.ttl = ipv4_key->ipv4_ttl;
 			swkey->ip.frag = ipv4_key->ipv4_frag;
 			swkey->ipv4.addr.src = ipv4_key->ipv4_src;
 			swkey->ipv4.addr.dst = ipv4_key->ipv4_dst;
@@ -976,6 +979,7 @@ int flow_from_nlattrs(struct sw_flow_key *swkey, int *key_lenp,
 			swkey->ipv6.label = ipv6_key->ipv6_label;
 			swkey->ip.proto = ipv6_key->ipv6_proto;
 			swkey->ip.tos = ipv6_key->ipv6_tos;
+			swkey->ip.ttl = ipv6_key->ipv6_ttl;
 			swkey->ip.frag = ipv6_key->ipv6_frag;
 			memcpy(&swkey->ipv6.addr.src, ipv6_key->ipv6_src,
 					sizeof(swkey->ipv6.addr.src));
@@ -1245,6 +1249,7 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
 		ipv4_key->ipv4_dst = swkey->ipv4.addr.dst;
 		ipv4_key->ipv4_proto = swkey->ip.proto;
 		ipv4_key->ipv4_tos = swkey->ip.tos;
+		ipv4_key->ipv4_ttl = swkey->ip.ttl;
 		ipv4_key->ipv4_frag = swkey->ip.frag;
 	} else if (swkey->eth.type == htons(ETH_P_IPV6)) {
 		struct ovs_key_ipv6 *ipv6_key;
@@ -1261,6 +1266,7 @@ int flow_to_nlattrs(const struct sw_flow_key *swkey, struct sk_buff *skb)
 		ipv6_key->ipv6_label = swkey->ipv6.label;
 		ipv6_key->ipv6_proto = swkey->ip.proto;
 		ipv6_key->ipv6_tos = swkey->ip.tos;
+		ipv6_key->ipv6_ttl = swkey->ip.ttl;
 		ipv6_key->ipv6_frag = swkey->ip.frag;
 	} else if (swkey->eth.type == htons(ETH_P_ARP)) {
 		struct ovs_key_arp *arp_key;
diff --git a/datapath/flow.h b/datapath/flow.h
index f12b11a..0bcb958 100644
--- a/datapath/flow.h
+++ b/datapath/flow.h
@@ -45,6 +45,7 @@ struct sw_flow_key {
 	struct {
 		u8     proto;		/* IP protocol or lower 8 bits of ARP opcode. */
 		u8     tos;         /* IP ToS DSCP in high 6 bits. */
+		u8     ttl;         /* IP TTL/hop limit. */
 		u8     frag;        /* OVS_FRAG_TYPE_* in low 2 bits. */
 	} ip;
 	union {
@@ -143,7 +144,7 @@ u64 flow_used_time(unsigned long flow_jiffies);
  *  OVS_KEY_ATTR_ETHERNET     12    --     4     16
  *  OVS_KEY_ATTR_8021Q         4    --     4      8
  *  OVS_KEY_ATTR_ETHERTYPE     2     2     4      8
- *  OVS_KEY_ATTR_IPV6         39     1     4     44
+ *  OVS_KEY_ATTR_IPV6         40    --     4     44
  *  OVS_KEY_ATTR_ICMPV6        2     2     4      8
  *  OVS_KEY_ATTR_ND           28    --     4     32
  *  -------------------------------------------------
diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h
index c2b4285..7236628 100644
--- a/include/linux/openvswitch.h
+++ b/include/linux/openvswitch.h
@@ -316,6 +316,7 @@ struct ovs_key_ipv4 {
 	__be32 ipv4_dst;
 	__u8   ipv4_proto;
 	__u8   ipv4_tos;
+	__u8   ipv4_ttl;
 	__u8   ipv4_frag;	/* One of OVS_FRAG_TYPE_*. */
 };
 
@@ -325,6 +326,7 @@ struct ovs_key_ipv6 {
 	__be32 ipv6_label;
 	__u8   ipv6_proto;
 	__u8   ipv6_tos;
+	__u8   ipv6_ttl;
 	__u8   ipv6_frag;	/* One of OVS_FRAG_TYPE_*. */
 };
 
diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h
index d0c4602..6a34268 100644
--- a/include/openflow/nicira-ext.h
+++ b/include/openflow/nicira-ext.h
@@ -1632,6 +1632,15 @@ OFP_ASSERT(sizeof(struct nx_action_output_reg) == 24);
  * Masking: Not maskable. */
 #define NXM_NX_IP_ECN      NXM_HEADER  (0x0001, 28, 1)
 
+/* The time-to-live/next hop of the IP header.
+ *
+ * Prereqs: NXM_OF_ETH_TYPE must be either 0x0800 or 0x86dd.
+ *
+ * Format: 8-bit integer.
+ *
+ * Masking: Not maskable. */
+#define NXM_NX_IP_TTL      NXM_HEADER  (0x0001, 29, 1)
+
 /* ## --------------------- ## */
 /* ## Requests and replies. ## */
 /* ## --------------------- ## */
diff --git a/lib/classifier.c b/lib/classifier.c
index 39a84d5..842166c 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -334,6 +334,13 @@ cls_rule_set_nw_ecn(struct cls_rule *rule, uint8_t nw_ecn)
 }
 
 void
+cls_rule_set_nw_ttl(struct cls_rule *rule, uint8_t nw_ttl)
+{
+    rule->wc.wildcards &= ~FWW_NW_TTL;
+    rule->flow.nw_ttl = nw_ttl;
+}
+
+void
 cls_rule_set_frag(struct cls_rule *rule, uint8_t frag)
 {
     rule->wc.frag_mask |= FLOW_FRAG_MASK;
@@ -480,7 +487,7 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
 
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     if (rule->priority != OFP_DEFAULT_PRIORITY) {
         ds_put_format(s, "priority=%d,", rule->priority);
@@ -631,6 +638,9 @@ cls_rule_format(const struct cls_rule *rule, struct ds *s)
     if (wc->tos_mask & IP_ECN_MASK) {
         ds_put_format(s, "nw_ecn=%"PRIu8",", f->tos & IP_ECN_MASK);
     }
+    if (!(w & FWW_NW_TTL)) {
+        ds_put_format(s, "nw_ttl=%"PRIu8",", f->nw_ttl);
+    }
     switch (wc->frag_mask) {
     case FLOW_FRAG_ANY | FLOW_FRAG_LATER:
         ds_put_format(s, "frag=%s,",
@@ -1177,7 +1187,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
     const flow_wildcards_t wc = wildcards->wildcards;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         if ((a->regs[i] ^ b->regs[i]) & wildcards->reg_masks[i]) {
@@ -1204,6 +1214,7 @@ flow_equal_except(const struct flow *a, const struct flow *b,
             && (wc & FWW_ETH_MCAST
                 || !((a->dl_dst[0] ^ b->dl_dst[0]) & 0x01))
             && (wc & FWW_NW_PROTO || a->nw_proto == b->nw_proto)
+            && (wc & FWW_NW_TTL || a->nw_ttl == b->nw_ttl)
             && !((a->tos ^ b->tos) & wildcards->tos_mask)
             && !((a->frag ^ b->frag) & wildcards->frag_mask)
             && (wc & FWW_ARP_SHA || eth_addr_equals(a->arp_sha, b->arp_sha))
diff --git a/lib/classifier.h b/lib/classifier.h
index 581ee83..d6ab74e 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -118,6 +118,7 @@ void cls_rule_set_nw_dst(struct cls_rule *, ovs_be32);
 bool cls_rule_set_nw_dst_masked(struct cls_rule *, ovs_be32 ip, ovs_be32 mask);
 void cls_rule_set_nw_dscp(struct cls_rule *, uint8_t);
 void cls_rule_set_nw_ecn(struct cls_rule *, uint8_t);
+void cls_rule_set_nw_ttl(struct cls_rule *, uint8_t);
 void cls_rule_set_frag(struct cls_rule *, uint8_t frag);
 void cls_rule_set_frag_masked(struct cls_rule *, uint8_t frag, uint8_t mask);
 void cls_rule_set_icmp_type(struct cls_rule *, uint8_t);
diff --git a/lib/flow.c b/lib/flow.c
index 8237546..e4d3c76 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -150,6 +150,7 @@ parse_ipv6(struct ofpbuf *packet, struct flow *flow)
     tc_flow = get_unaligned_be32(&nh->ip6_flow);
     flow->tos = ntohl(tc_flow) >> 4;
     flow->ipv6_label = tc_flow & htonl(IPV6_LABEL_MASK);
+    flow->nw_ttl = nh->ip6_hlim;
     flow->nw_proto = IPPROTO_NONE;
 
     while (1) {
@@ -376,6 +377,7 @@ flow_extract(struct ofpbuf *packet, uint32_t priority, ovs_be64 tun_id,
                     flow->frag |= FLOW_FRAG_LATER;
                 }
             }
+            flow->nw_ttl = nh->ip_ttl;
 
             if (!(nh->ip_frag_off & htons(IP_FRAG_OFF_MASK))) {
                 if (flow->nw_proto == IPPROTO_TCP) {
@@ -437,7 +439,7 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
     const flow_wildcards_t wc = wildcards->wildcards;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     for (i = 0; i < FLOW_N_REGS; i++) {
         flow->regs[i] &= wildcards->reg_masks[i];
@@ -475,6 +477,9 @@ flow_zero_wildcards(struct flow *flow, const struct flow_wildcards *wildcards)
         flow->ipv6_label = htonl(0);
     }
     flow->tos &= wildcards->tos_mask;
+    if (wc & FWW_NW_TTL) {
+        flow->nw_ttl = 0;
+    }
     flow->frag &= wildcards->frag_mask;
     if (wc & FWW_ARP_SHA) {
         memset(flow->arp_sha, 0, sizeof flow->arp_sha);
@@ -525,8 +530,10 @@ flow_format(struct ds *ds, const struct flow *flow)
                   ntohs(flow->dl_type));
 
     if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
-        ds_put_format(ds, " label%#"PRIx32" proto%"PRIu8" tos%"PRIu8" ipv6",
-                      ntohl(flow->ipv6_label), flow->nw_proto, flow->tos);
+        ds_put_format(ds, " label%#"PRIx32" proto%"PRIu8" tos%"PRIu8
+                      " ttl%"PRIu8" ipv6",
+                      ntohl(flow->ipv6_label), flow->nw_proto,
+                      flow->tos, flow->nw_ttl);
         print_ipv6_addr(ds, &flow->ipv6_src);
         ds_put_cstr(ds, "->");
         print_ipv6_addr(ds, &flow->ipv6_dst);
@@ -534,8 +541,9 @@ flow_format(struct ds *ds, const struct flow *flow)
     } else {
         ds_put_format(ds, " proto%"PRIu8
                           " tos%"PRIu8
+                          " ttl%"PRIu8
                           " ip"IP_FMT"->"IP_FMT,
-                      flow->nw_proto, flow->tos,
+                      flow->nw_proto, flow->tos, flow->nw_ttl,
                       IP_ARGS(&flow->nw_src),
                       IP_ARGS(&flow->nw_dst));
     }
@@ -570,7 +578,7 @@ flow_print(FILE *stream, const struct flow *flow)
 void
 flow_wildcards_init_catchall(struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     wc->wildcards = FWW_ALL;
     wc->tun_id_mask = htonll(0);
@@ -590,7 +598,7 @@ flow_wildcards_init_catchall(struct flow_wildcards *wc)
 void
 flow_wildcards_init_exact(struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     wc->wildcards = 0;
     wc->tun_id_mask = htonll(UINT64_MAX);
@@ -612,7 +620,7 @@ flow_wildcards_is_exact(const struct flow_wildcards *wc)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     if (wc->wildcards
         || wc->tun_id_mask != htonll(UINT64_MAX)
@@ -642,7 +650,7 @@ flow_wildcards_is_catchall(const struct flow_wildcards *wc)
 {
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     if (wc->wildcards != FWW_ALL
         || wc->tun_id_mask != htonll(0)
diff --git a/lib/flow.h b/lib/flow.h
index 248233e..52bda62 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -35,7 +35,7 @@ struct ofpbuf;
 /* This sequence number should be incremented whenever anything involving flows
  * or the wildcarding of flows changes.  This will cause build assertion
  * failures in places which likely need to be updated. */
-#define FLOW_WC_SEQ 5
+#define FLOW_WC_SEQ 6
 
 #define FLOW_N_REGS 5
 BUILD_ASSERT_DECL(FLOW_N_REGS <= NXM_NX_MAX_REGS);
@@ -71,22 +71,23 @@ struct flow {
     uint8_t dl_dst[6];          /* Ethernet destination address. */
     uint8_t nw_proto;           /* IP protocol or low 8 bits of ARP opcode. */
     uint8_t tos;                /* IP ToS. */
+    uint8_t nw_ttl;             /* IP TTL/Hop Limit. */
     uint8_t frag;               /* FLOW_FRAG_*. */
     uint8_t arp_sha[6];         /* ARP/ND source hardware address. */
     uint8_t arp_tha[6];         /* ARP/ND target hardware address. */
-    uint8_t reserved[7];        /* Reserved for 64-bit packing. */
+    uint8_t reserved[6];        /* Reserved for 64-bit packing. */
 };
 
 /* Assert that there are FLOW_SIG_SIZE bytes of significant data in "struct
  * flow", followed by FLOW_PAD_SIZE bytes of padding. */
-#define FLOW_SIG_SIZE (109 + FLOW_N_REGS * 4)
-#define FLOW_PAD_SIZE 7
+#define FLOW_SIG_SIZE (110 + FLOW_N_REGS * 4)
+#define FLOW_PAD_SIZE 6
 BUILD_ASSERT_DECL(offsetof(struct flow, arp_tha) == FLOW_SIG_SIZE - 6);
 BUILD_ASSERT_DECL(sizeof(((struct flow *)0)->arp_tha) == 6);
 BUILD_ASSERT_DECL(sizeof(struct flow) == FLOW_SIG_SIZE + FLOW_PAD_SIZE);
 
 /* Remember to update FLOW_WC_SEQ when changing 'struct flow'. */
-BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 129 && FLOW_WC_SEQ == 5);
+BUILD_ASSERT_DECL(FLOW_SIG_SIZE == 130 && FLOW_WC_SEQ == 6);
 
 void flow_extract(struct ofpbuf *, uint32_t priority, ovs_be64 tun_id,
                   uint16_t in_port, struct flow *);
@@ -143,10 +144,11 @@ typedef unsigned int OVS_BITWISE flow_wildcards_t;
 #define FWW_ARP_THA     ((OVS_FORCE flow_wildcards_t) (1 << 9))
 #define FWW_ND_TARGET   ((OVS_FORCE flow_wildcards_t) (1 << 10))
 #define FWW_IPV6_LABEL  ((OVS_FORCE flow_wildcards_t) (1 << 11))
-#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 12)) - 1))
+#define FWW_NW_TTL      ((OVS_FORCE flow_wildcards_t) (1 << 12))
+#define FWW_ALL         ((OVS_FORCE flow_wildcards_t) (((1 << 13)) - 1))
 
 /* Remember to update FLOW_WC_SEQ when adding or removing FWW_*. */
-BUILD_ASSERT_DECL(FWW_ALL == ((1 << 12) - 1) && FLOW_WC_SEQ == 5);
+BUILD_ASSERT_DECL(FWW_ALL == ((1 << 13) - 1) && FLOW_WC_SEQ == 6);
 
 /* Information on wildcards for a flow, as a supplement to "struct flow".
  *
@@ -167,7 +169,7 @@ struct flow_wildcards {
 };
 
 /* Remember to update FLOW_WC_SEQ when updating struct flow_wildcards. */
-BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 80 && FLOW_WC_SEQ == 5);
+BUILD_ASSERT_DECL(sizeof(struct flow_wildcards) == 80 && FLOW_WC_SEQ == 6);
 
 void flow_wildcards_init_catchall(struct flow_wildcards *);
 void flow_wildcards_init_exact(struct flow_wildcards *);
diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index 6e43c86..36a419f 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -202,6 +202,13 @@ static const struct mf_field mf_fields[MFF_N_IDS] = {
         MFP_IP_ANY,
         NXM_NX_IP_ECN,
     }, {
+        MFF_IP_TTL, "nw_ttl", NULL,
+        MF_FIELD_SIZES(u8),
+        MFM_NONE, FWW_NW_TTL,
+        MFS_DECIMAL,
+        MFP_IP_ANY,
+        NXM_NX_IP_TTL,
+    }, {
         MFF_IP_FRAG, "ip_frag", NULL,
         1, 2,
         MFM_FULLY, 0,
@@ -369,6 +376,7 @@ mf_is_all_wild(const struct mf_field *mf, const struct flow_wildcards *wc)
     case MFF_ETH_SRC:
     case MFF_ETH_TYPE:
     case MFF_IP_PROTO:
+    case MFF_IP_TTL:
     case MFF_IPV6_LABEL:
     case MFF_ARP_OP:
     case MFF_ARP_SHA:
@@ -462,6 +470,7 @@ mf_get_mask(const struct mf_field *mf, const struct flow_wildcards *wc,
     case MFF_ETH_SRC:
     case MFF_ETH_TYPE:
     case MFF_IP_PROTO:
+    case MFF_IP_TTL:
     case MFF_IPV6_LABEL:
     case MFF_ARP_OP:
     case MFF_ARP_SHA:
@@ -688,6 +697,7 @@ mf_is_value_valid(const struct mf_field *mf, const union mf_value *value)
     case MFF_IPV6_SRC:
     case MFF_IPV6_DST:
     case MFF_IP_PROTO:
+    case MFF_IP_TTL:
     case MFF_ARP_SPA:
     case MFF_ARP_TPA:
     case MFF_ARP_SHA:
@@ -820,6 +830,10 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
         value->u8 = flow->tos & IP_ECN_MASK;
         break;
 
+    case MFF_IP_TTL:
+        value->u8 = flow->nw_ttl;
+        break;
+
     case MFF_IP_FRAG:
         value->u8 = flow->frag;
         break;
@@ -975,6 +989,10 @@ mf_set_value(const struct mf_field *mf,
         cls_rule_set_nw_ecn(rule, value->u8);
         break;
 
+    case MFF_IP_TTL:
+        cls_rule_set_nw_ttl(rule, value->u8);
+        break;
+
     case MFF_IP_FRAG:
         cls_rule_set_frag(rule, value->u8);
         break;
@@ -1148,6 +1166,11 @@ mf_set_wild(const struct mf_field *mf, struct cls_rule *rule)
         rule->flow.tos &= ~IP_ECN_MASK;
         break;
 
+    case MFF_IP_TTL:
+        rule->wc.wildcards |= FWW_NW_TTL;
+        rule->flow.nw_ttl = 0;
+        break;
+
     case MFF_IP_FRAG:
         rule->wc.frag_mask |= FLOW_FRAG_MASK;
         rule->flow.frag &= ~FLOW_FRAG_MASK;
@@ -1227,6 +1250,7 @@ mf_set(const struct mf_field *mf,
     case MFF_VLAN_PCP:
     case MFF_IPV6_LABEL:
     case MFF_IP_PROTO:
+    case MFF_IP_TTL:
     case MFF_IP_DSCP:
     case MFF_IP_ECN:
     case MFF_ARP_OP:
@@ -1432,6 +1456,7 @@ mf_random_value(const struct mf_field *mf, union mf_value *value)
     case MFF_IPV6_SRC:
     case MFF_IPV6_DST:
     case MFF_IP_PROTO:
+    case MFF_IP_TTL:
     case MFF_ARP_SPA:
     case MFF_ARP_TPA:
     case MFF_ARP_SHA:
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index 42523fc..61be956 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -72,6 +72,7 @@ enum mf_field_id {
     MFF_IP_PROTO,               /* u8 (used for IPv4 or IPv6) */
     MFF_IP_DSCP,                /* u8 (used for IPv4 or IPv6) */
     MFF_IP_ECN,                 /* u8 (used for IPv4 or IPv6) */
+    MFF_IP_TTL,                 /* u8 (used for IPv4 or IPv6) */
     MFF_IP_FRAG,                /* u8 (used for IPv4 or IPv6) */
 
     MFF_ARP_OP,                 /* be16 */
diff --git a/lib/nx-match.c b/lib/nx-match.c
index cb73084..9919e1f 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -460,7 +460,7 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr)
     int match_len;
     int i;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     /* Metadata. */
     if (!(wc & FWW_IN_PORT)) {
@@ -496,6 +496,10 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr)
             nxm_put_8(b, NXM_NX_IP_ECN, flow->tos & IP_ECN_MASK);
         }
 
+        if (!(wc & FWW_NW_TTL)) {
+            nxm_put_8(b, NXM_NX_IP_TTL, flow->nw_ttl);
+        }
+
         if (!(wc & FWW_NW_PROTO)) {
             nxm_put_8(b, NXM_OF_IP_PROTO, flow->nw_proto);
             switch (flow->nw_proto) {
@@ -550,6 +554,10 @@ nx_put_match(struct ofpbuf *b, const struct cls_rule *cr)
             nxm_put_8(b, NXM_NX_IP_ECN, flow->tos & IP_ECN_MASK);
         }
 
+        if (!(wc & FWW_NW_TTL)) {
+            nxm_put_8(b, NXM_NX_IP_TTL, flow->nw_ttl);
+        }
+
         if (!(wc & FWW_NW_PROTO)) {
             nxm_put_8(b, NXM_OF_IP_PROTO, flow->nw_proto);
             switch (flow->nw_proto) {
@@ -1058,6 +1066,9 @@ nxm_read_field(const struct nxm_field *src, const struct flow *flow)
     case NFI_NXM_NX_IP_ECN:
         return flow->tos & IP_ECN_MASK;
 
+    case NFI_NXM_NX_IP_TTL:
+        return flow->nw_ttl;
+
     case NFI_NXM_NX_IP_FRAG:
         return flow->frag;
 
@@ -1218,6 +1229,10 @@ nxm_write_field(const struct nxm_field *dst, struct flow *flow,
         flow->tos |= new_value & IP_ECN_MASK;
         break;
 
+    case NFI_NXM_NX_IP_TTL:
+        flow->nw_ttl = new_value;
+        break;
+
     case NFI_NXM_NX_IP_FRAG:
         flow->frag = new_value;
         break;
diff --git a/lib/nx-match.def b/lib/nx-match.def
index 3691043..ea032fc 100644
--- a/lib/nx-match.def
+++ b/lib/nx-match.def
@@ -46,6 +46,7 @@ DEFINE_FIELD_M(NX_IPV6_SRC,   MFF_IPV6_SRC,  false)
 DEFINE_FIELD_M(NX_IPV6_DST,   MFF_IPV6_DST,  false)
 DEFINE_FIELD  (NX_IPV6_LABEL, MFF_IPV6_LABEL,false)
 DEFINE_FIELD  (NX_IP_ECN,     MFF_IP_ECN,     true)
+DEFINE_FIELD  (NX_IP_TTL,     MFF_IP_TTL,     true)
 /* XXX should we have MFF_ICMPV4_TYPE and MFF_ICMPV6_TYPE? */
 DEFINE_FIELD  (NX_ICMPV6_TYPE,MFF_ICMP_TYPE, false)
 DEFINE_FIELD  (NX_ICMPV6_CODE,MFF_ICMP_CODE, false)
diff --git a/lib/nx-match.h b/lib/nx-match.h
index 6a33dbe..0b9a437 100644
--- a/lib/nx-match.h
+++ b/lib/nx-match.h
@@ -103,6 +103,7 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits)
  *  NXM_OF_VLAN_TCI     4       2     2      8
  *  NXM_OF_IP_TOS       4       1    --      5
  *  NXM_NX_IP_ECN       4       1    --      5
+ *  NXM_OF_IP_TTL       4       1    --      5
  *  NXM_NX_IP_FRAG      4       1     1      8
  *  NXM_OF_IP_PROTO     4       2    --      6
  *  NXM_OF_IPV6_SRC_W   4      16    16     36
@@ -119,7 +120,7 @@ nxm_decode_n_bits(ovs_be16 ofs_nbits)
  *  NXM_NX_REG_W(4)     4       4     4     12
  *  NXM_NX_TUN_ID_W     4       8     8     20
  *  -------------------------------------------
- *  total                                  270
+ *  total                                  275
  *
  * So this value is conservative.
  */
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 29abce4..612cc5c 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -369,11 +369,12 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
 
     case OVS_KEY_ATTR_IPV4:
         ipv4_key = nl_attr_get(a);
-        ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT","
-                      "proto=%"PRId8",tos=0x%"PRIx8",frag=%s)",
+        ds_put_format(ds, "ipv4(src="IP_FMT",dst="IP_FMT",proto=%"PRId8
+                      ",tos=0x%"PRIx8",ttl=%"PRIu8",frag=%s)",
                       IP_ARGS(&ipv4_key->ipv4_src),
                       IP_ARGS(&ipv4_key->ipv4_dst),
                       ipv4_key->ipv4_proto, ipv4_key->ipv4_tos,
+                      ipv4_key->ipv4_ttl,
                       ovs_frag_type_to_string(ipv4_key->ipv4_frag));
         break;
 
@@ -386,9 +387,10 @@ format_odp_key_attr(const struct nlattr *a, struct ds *ds)
         inet_ntop(AF_INET6, ipv6_key->ipv6_dst, dst_str, sizeof dst_str);
 
         ds_put_format(ds, "ipv6(src=%s,dst=%s,label=0x%"PRIx32",proto=%"PRId8
-                      ",tos=0x%"PRIx8",frag=%s)",
+                      ",tos=0x%"PRIx8",ttl=%"PRIu8",frag=%s)",
                       src_str, dst_str, ntohl(ipv6_key->ipv6_label),
                       ipv6_key->ipv6_proto, ipv6_key->ipv6_tos,
+                      ipv6_key->ipv6_ttl,
                       ovs_frag_type_to_string(ipv6_key->ipv6_frag));
         break;
     }
@@ -606,14 +608,15 @@ parse_odp_key_attr(const char *s, struct ofpbuf *key)
         ovs_be32 ipv4_dst;
         int ipv4_proto;
         int ipv4_tos;
+        int ipv4_ttl;
         char frag[8];
         enum ovs_frag_type ipv4_frag;
         int n = -1;
 
         if (sscanf(s, "ipv4(src="IP_SCAN_FMT",dst="IP_SCAN_FMT","
-                   "proto=%i,tos=%i,frag=%7[a-z])%n",
+                   "proto=%i,tos=%i,ttl=%i,frag=%7[a-z])%n",
                    IP_SCAN_ARGS(&ipv4_src), IP_SCAN_ARGS(&ipv4_dst),
-                   &ipv4_proto, &ipv4_tos, frag, &n) > 0
+                   &ipv4_proto, &ipv4_tos, &ipv4_ttl, frag, &n) > 0
             && n > 0
             && ovs_frag_type_from_string(frag, &ipv4_frag)) {
             struct ovs_key_ipv4 ipv4_key;
@@ -623,6 +626,7 @@ parse_odp_key_attr(const char *s, struct ofpbuf *key)
             ipv4_key.ipv4_dst = ipv4_dst;
             ipv4_key.ipv4_proto = ipv4_proto;
             ipv4_key.ipv4_tos = ipv4_tos;
+            ipv4_key.ipv4_ttl = ipv4_ttl;
             ipv4_key.ipv4_frag = ipv4_frag;
             nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV4,
                               &ipv4_key, sizeof ipv4_key);
@@ -636,14 +640,15 @@ parse_odp_key_attr(const char *s, struct ofpbuf *key)
         int ipv6_label;
         int ipv6_proto;
         int ipv6_tos;
+        int ipv6_ttl;
         char frag[8];
         enum ovs_frag_type ipv6_frag;
         int n = -1;
 
         if (sscanf(s, "ipv6(src="IPV6_SCAN_FMT",dst="IPV6_SCAN_FMT","
-                   "label=%i,proto=%i,tos=%i,frag=%7[a-z])%n",
+                   "label=%i,proto=%i,tos=%i,ttl=%i,frag=%7[a-z])%n",
                    ipv6_src_s, ipv6_dst_s, &ipv6_label,
-                   &ipv6_proto, &ipv6_tos, frag, &n) > 0
+                   &ipv6_proto, &ipv6_tos, &ipv6_ttl, frag, &n) > 0
             && n > 0
             && ovs_frag_type_from_string(frag, &ipv6_frag)) {
             struct ovs_key_ipv6 ipv6_key;
@@ -656,6 +661,7 @@ parse_odp_key_attr(const char *s, struct ofpbuf *key)
             ipv6_key.ipv6_label = htonl(ipv6_label);
             ipv6_key.ipv6_proto = ipv6_proto;
             ipv6_key.ipv6_tos = ipv6_tos;
+            ipv6_key.ipv6_ttl = ipv6_ttl;
             ipv6_key.ipv6_frag = ipv6_frag;
             nl_msg_put_unspec(key, OVS_KEY_ATTR_IPV6,
                               &ipv6_key, sizeof ipv6_key);
@@ -877,6 +883,7 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
         ipv4_key->ipv4_dst = flow->nw_dst;
         ipv4_key->ipv4_proto = flow->nw_proto;
         ipv4_key->ipv4_tos = flow->tos;
+        ipv4_key->ipv4_ttl = flow->nw_ttl;
         ipv4_key->ipv4_frag = ovs_to_odp_frag(flow->frag);
     } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
         struct ovs_key_ipv6 *ipv6_key;
@@ -889,6 +896,7 @@ odp_flow_key_from_flow(struct ofpbuf *buf, const struct flow *flow)
         ipv6_key->ipv6_label = flow->ipv6_label;
         ipv6_key->ipv6_proto = flow->nw_proto;
         ipv6_key->ipv6_tos = flow->tos;
+        ipv6_key->ipv6_ttl = flow->nw_ttl;
         ipv6_key->ipv6_frag = ovs_to_odp_frag(flow->frag);
     } else if (flow->dl_type == htons(ETH_TYPE_ARP)) {
         struct ovs_key_arp *arp_key;
@@ -1061,6 +1069,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
             flow->nw_dst = ipv4_key->ipv4_dst;
             flow->nw_proto = ipv4_key->ipv4_proto;
             flow->tos = ipv4_key->ipv4_tos;
+            flow->nw_ttl = ipv4_key->ipv4_ttl;
             if (!odp_to_ovs_frag(ipv4_key->ipv4_frag, flow)) {
                 return EINVAL;
             }
@@ -1076,6 +1085,7 @@ odp_flow_key_to_flow(const struct nlattr *key, size_t key_len,
             flow->ipv6_label = ipv6_key->ipv6_label;
             flow->nw_proto = ipv6_key->ipv6_proto;
             flow->tos = ipv6_key->ipv6_tos;
+            flow->nw_ttl = ipv6_key->ipv6_ttl;
             if (!odp_to_ovs_frag(ipv6_key->ipv6_frag, flow)) {
                 return EINVAL;
             }
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 0146a33..4021551 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -360,6 +360,7 @@ parse_named_action(enum ofputil_action_code code, const struct flow *flow,
     case OFPUTIL_NXAST_LEARN:
         learn_parse(b, arg, flow);
         break;
+
     case OFPUTIL_NXAST_EXIT:
         ofputil_put_NXAST_EXIT(b);
         break;
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index 1b16279..fac21af 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -101,15 +101,15 @@ static const flow_wildcards_t WC_INVARIANTS = 0
 void
 ofputil_wildcard_from_openflow(uint32_t ofpfw, struct flow_wildcards *wc)
 {
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     /* Initialize most of rule->wc. */
     flow_wildcards_init_catchall(wc);
     wc->wildcards = (OVS_FORCE flow_wildcards_t) ofpfw & WC_INVARIANTS;
 
     /* Wildcard fields that aren't defined by ofp_match or tun_id. */
-    wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_ND_TARGET
-                      | FWW_IPV6_LABEL);
+    wc->wildcards |= (FWW_ARP_SHA | FWW_ARP_THA | FWW_NW_TTL
+                      | FWW_ND_TARGET | FWW_IPV6_LABEL);
 
     if (!(ofpfw & OFPFW_NW_TOS)) {
         wc->tos_mask |= IP_DSCP_MASK;
@@ -859,7 +859,7 @@ ofputil_min_flow_format(const struct cls_rule *rule)
 {
     const struct flow_wildcards *wc = &rule->wc;
 
-    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 5);
+    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 6);
 
     /* Only NXM supports separately wildcards the Ethernet multicast bit. */
     if (!(wc->wildcards & FWW_DL_DST) != !(wc->wildcards & FWW_ETH_MCAST)) {
@@ -902,6 +902,11 @@ ofputil_min_flow_format(const struct cls_rule *rule)
         return NXFF_NXM;
     }
 
+    /* Only NXM supports matching IP TTL/hop limit. */
+    if (!(wc->wildcards & FWW_NW_TTL)) {
+        return NXFF_NXM;
+    }
+
     /* Other formats can express this rule. */
     return NXFF_OPENFLOW10;
 }
@@ -2516,7 +2521,7 @@ ofputil_normalize_rule(struct cls_rule *rule, enum nx_flow_format flow_format)
         MAY_NW_ADDR     = 1 << 0, /* nw_src, nw_dst */
         MAY_TP_ADDR     = 1 << 1, /* tp_src, tp_dst */
         MAY_NW_PROTO    = 1 << 2, /* nw_proto */
-        MAY_IPVx        = 1 << 3, /* tos, frag */
+        MAY_IPVx        = 1 << 3, /* tos, frag, ttl */
         MAY_ARP_SHA     = 1 << 4, /* arp_sha */
         MAY_ARP_THA     = 1 << 5, /* arp_tha */
         MAY_IPV6_ADDR   = 1 << 6, /* ipv6_src, ipv6_dst */
@@ -2570,6 +2575,7 @@ ofputil_normalize_rule(struct cls_rule *rule, enum nx_flow_format flow_format)
     if (!(may_match & MAY_IPVx)) {
         wc.tos_mask = 0;
         wc.frag_mask = 0;
+        wc.wildcards |= FWW_NW_TTL;
     }
     if (!(may_match & MAY_ARP_SHA)) {
         wc.wildcards |= FWW_ARP_SHA;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index d43a596..6cb582d 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -3670,6 +3670,7 @@ commit_set_nw_action(const struct flow *flow, struct flow *base,
     if (base->nw_src == flow->nw_src &&
         base->nw_dst == flow->nw_dst &&
         base->tos == flow->tos &&
+        base->nw_ttl == flow->nw_ttl &&
         base->frag == flow->frag) {
         return;
     }
@@ -3680,6 +3681,7 @@ commit_set_nw_action(const struct flow *flow, struct flow *base,
     ipv4_key.ipv4_dst = base->nw_dst = flow->nw_dst;
     ipv4_key.ipv4_proto = base->nw_proto;
     ipv4_key.ipv4_tos = flow->tos;
+    ipv4_key.ipv4_ttl = flow->nw_ttl;
     ipv4_key.ipv4_frag = (base->frag == 0 ? OVS_FRAG_TYPE_NONE
                           : base->frag == FLOW_FRAG_ANY ? OVS_FRAG_TYPE_FIRST
                           : OVS_FRAG_TYPE_LATER);
diff --git a/tests/odp.at b/tests/odp.at
index edbd401..8dbfb4c 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -4,24 +4,24 @@ AT_SETUP([OVS datapath parsing and formatting - valid forms])
 AT_DATA([odp-base.txt], [dnl
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x1234)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,frag=no)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x81,frag=no)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,frag=first)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,frag=later)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0x0,frag=no),tcp(src=80,dst=8080)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0x0,frag=no),udp(src=81,dst=6632)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=1,tos=0x0,frag=no),icmp(type=1,code=2)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,frag=no)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x71,frag=no)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,frag=first)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,frag=later)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=6,tos=0x0,frag=no),tcp(src=80,dst=8080)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=17,tos=0x0,frag=no),udp(src=6630,dst=22)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,frag=no),icmpv6(type=1,code=2)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,frag=no),icmpv6(type=135,code=0),nd(target=::3)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,frag=no),icmpv6(type=135,code=0),nd(target=::3,sll=00:05:06:07:08:09)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,frag=no),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
-in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,ttl=128,frag=no)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x81,ttl=128,frag=no)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,ttl=128,frag=first)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=5,tos=0x80,ttl=128,frag=later)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=6,tos=0x0,ttl=128,frag=no),tcp(src=80,dst=8080)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=17,tos=0x0,ttl=128,frag=no),udp(src=81,dst=6632)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0800),ipv4(src=35.8.2.41,dst=172.16.0.20,proto=1,tos=0x0,ttl=128,frag=no),icmp(type=1,code=2)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,ttl=128,frag=no)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x71,ttl=128,frag=no)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,ttl=128,frag=first)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=10,tos=0x70,ttl=128,frag=later)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=6,tos=0x0,ttl=128,frag=no),tcp(src=80,dst=8080)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=17,tos=0x0,ttl=128,frag=no),udp(src=6630,dst=22)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,ttl=128,frag=no),icmpv6(type=1,code=2)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,ttl=128,frag=no),icmpv6(type=135,code=0),nd(target=::3)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,ttl=128,frag=no),icmpv6(type=135,code=0),nd(target=::3,sll=00:05:06:07:08:09)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,ttl=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,tll=00:0a:0b:0c:0d:0e)
+in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x86dd),ipv6(src=::1,dst=::2,label=0x0,proto=58,tos=0x0,ttl=128,frag=no),icmpv6(type=136,code=0),nd(target=::3,sll=00:05:06:07:08:09,tll=00:0a:0b:0c:0d:0e)
 in_port(1),eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15),eth_type(0x0806),arp(sip=1.2.3.4,tip=5.6.7.8,op=1,sha=00:0f:10:11:12:13,tha=00:14:15:16:17:18)
 ])
 
diff --git a/tests/ofp-print.at b/tests/ofp-print.at
index d8e2f35..df5dc9b 100644
--- a/tests/ofp-print.at
+++ b/tests/ofp-print.at
@@ -237,7 +237,7 @@ dnl The tcpdump output format differs slightly from one version to another,
 dnl so trim off the end of the line where differences appear.
 AT_CHECK([sed 's/\(length 60:\).*/\1 .../' stdout], [0], [dnl
 OFPT_PACKET_IN (xid=0x0): total_len=60 in_port=3 data_len=60 buffer=0x00000111
-priority0:tunnel0:in_port0003:tci(0) mac50:54:00:00:00:05->50:54:00:00:00:06 type0800 proto6 tos0 ip192.168.0.1->192.168.0.2 port10031->0
+priority0:tunnel0:in_port0003:tci(0) mac50:54:00:00:00:05->50:54:00:00:00:06 type0800 proto6 tos0 ttl64 ip192.168.0.1->192.168.0.2 port10031->0
 50:54:00:00:00:05 > 50:54:00:00:00:06, ethertype IPv4 (0x0800), length 60: ...
 ])
 AT_CLEANUP
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index d08097b..e6c2f92 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -11,7 +11,7 @@ table=1 in_port=2 priority=1500 icmp actions=output(17),resubmit(,2)
 table=1 in_port=3 priority=1500 icmp actions=output(14),resubmit(,2)
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 10,11,12,13,14,15,16,17,18,19,20,21
 ])
@@ -36,7 +36,7 @@ in_port=10,reg1=0xdeadbeef actions=output:21
 in_port=11,reg2=0xeef22dea actions=output:22
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 20,21,22
 ])
@@ -55,7 +55,7 @@ in_port=6 actions=output:NXM_NX_REG0[[0..15]],output:NXM_NX_REG0[[16..31]]
 in_port=7 actions=load:0x110000ff->NXM_NX_REG0[[]],output:NXM_NX_REG0[[]]
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 9,55,10,55,66,11,77,88
 ])
@@ -73,7 +73,7 @@ in_port=4 actions=set_tunnel:4,set_tunnel:3,output:4
 in_port=5 actions=set_tunnel:5
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'tun_id(0x1),in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'tun_id(0x1),in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: set(tun_id(0x1)),1,2,set(tun_id(0x3)),3,4
 ])
@@ -239,7 +239,7 @@ priority=50 tcp ip_frag=later           actions=output:6
 ])
 AT_CHECK([ovs-ofctl replace-flows br0 flows.txt])
 
-base_flow="in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0"
+base_flow="in_port(90),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=6,tos=0,ttl=128"
 no_flow="$base_flow,frag=no),tcp(src=12345,dst=80)"
 first_flow="$base_flow,frag=first),tcp(src=12345,dst=80)"
 later_flow="$base_flow,frag=later)"
@@ -275,15 +275,15 @@ in_port=2 actions=output:12,resubmit:1,output:12
 in_port=3 actions=output:13,resubmit:2,output:14
 ])
 AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 10
 ])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(2),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 12,10
 ])
-AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(3),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,frag=no),icmp(type=8,code=0)'], [0], [stdout])
+AT_CHECK([ovs-appctl ofproto/trace br0 'in_port(3),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=1,tos=0,ttl=128,frag=no),icmp(type=8,code=0)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: 13,12,10
 ])
diff --git a/tests/ovs-ofctl.at b/tests/ovs-ofctl.at
index 39093a8..2a458c8 100644
--- a/tests/ovs-ofctl.at
+++ b/tests/ovs-ofctl.at
@@ -224,6 +224,11 @@ NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(01)
 NXM_OF_ETH_TYPE(0800) NXM_OF_IP_PROTO(05)
 NXM_OF_IP_PROTO(05)
 
+# IP TTL
+NXM_OF_ETH_TYPE(0800) NXM_NX_IP_TTL(80)
+NXM_OF_ETH_TYPE(86dd) NXM_NX_IP_TTL(ff)
+NXM_NX_IP_TTL(80)
+
 # IP source
 NXM_OF_ETH_TYPE(0800) NXM_OF_IP_SRC(ac100014)
 NXM_OF_ETH_TYPE(0800) NXM_OF_IP_SRC_W(C0a80000/FFFF0000)
@@ -408,6 +413,11 @@ NXM_OF_ETH_TYPE(0800), NXM_OF_IP_PROTO(01)
 NXM_OF_ETH_TYPE(0800), NXM_OF_IP_PROTO(05)
 nx_pull_match() returned error 44010104 (type OFPET_BAD_REQUEST, code NXBRC_NXM_BAD_PREREQ)
 
+# IP TTL
+NXM_OF_ETH_TYPE(0800), NXM_NX_IP_TTL(80)
+NXM_OF_ETH_TYPE(86dd), NXM_NX_IP_TTL(ff)
+nx_pull_match() returned error 44010104 (type OFPET_BAD_REQUEST, code NXBRC_NXM_BAD_PREREQ)
+
 # IP source
 NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC(ac100014)
 NXM_OF_ETH_TYPE(0800), NXM_OF_IP_SRC_W(c0a80000/ffff0000)
diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in
index 30f9c58..2f9a0a3 100644
--- a/utilities/ovs-ofctl.8.in
+++ b/utilities/ovs-ofctl.8.in
@@ -416,6 +416,15 @@ When \fBdl_type\fR is wildcarded or set to a value other than 0x0800 or
 0x86dd, the value of \fBnw_ecn\fR is ignored (see \fBFlow Syntax\fR
 above).
 .
+.IP \fBnw_ttl=\fIttl\fR
+Matches IP TTL or IPv6 hop limit value \fIttl\fR, which is 
+specified as a decimal number between 0 and 255, inclusive.
+.IP
+When \fBdl_type\fR is wildcarded or set to a value other than 0x0800 or
+0x86dd, the value of \fBnw_ttl\fR is ignored (see \fBFlow Syntax\fR
+above).
+.IP
+.
 .IP \fBtp_src=\fIport\fR
 .IQ \fBtp_dst=\fIport\fR
 When \fBdl_type\fR and \fBnw_proto\fR specify TCP or UDP, \fBtp_src\fR
-- 
1.7.1




More information about the dev mailing list