Fix setting transport ports with frags.
authorJarno Rajahalme <jrajahalme@nicira.com>
Wed, 5 Nov 2014 18:10:13 +0000 (10:10 -0800)
committerJarno Rajahalme <jrajahalme@nicira.com>
Mon, 10 Nov 2014 21:40:03 +0000 (13:40 -0800)
Packets with 'LATER' fragment do not have a transport header, so it is
not possible to either match on or set transport ports on such
packets.  Matching is prevented by augmenting mf_are_prereqs_ok() with
a nw_frag 'LATER' bit check.  Setting the transport headers on such
packets is prevented in three ways:

1. Flows with an explicit match on nw_frag, where the LATER bit is 1:
   existing calls to the modified mf_are_prereqs_ok() prohibit using
   transport header fields (port numbers) in OXM/NXM actions
   (set_field, move).  SET_TP_* actions need a new check on the LATER
   bit.

2. Flows that wildcard the nw_frag LATER bit: At flow translation
   time, add calls to mf_are_prereqs_ok() to make sure that we do not
   use transport ports in flows that do not have them.

3. At action execution time, do not set transport ports, if the packet
   does not have a full transport header.  This ensures that we never
   call the packet_set functions, that require a valid transport
   header, with packets that do not have them.  For example, if the
   flow was created with a IPv6 first fragment that had the full TCP
   header, but the next packet's first fragment is missing them.

3 alone would suffice for correct behavior, but 1 and 2 seem like a
right thing to do, anyway.

Currently, if we are setting port numbers, we will also match them,
due to us tracking the set fields with the same flow_wildcards as the
matched fields.  Hence, if the incoming port number was not zero, the
flow would not match any packets with missing or truncated transport
headers.  However, relying on no packets having zero port numbers
would not be very robust.  Also, we may separate the tracking of set
and matched fields in the future, which would allow some flows that
blindly set port numbers to not match on them at all.

For TCP in case 3 we use ofpbuf_get_tcp_payload() that requires the
whole (potentially variable size) TCP header to be present.  However,
when parsing a flow, we only require the fixed size portion of the TCP
header to be present, which would be enough to set the port numbers
and fix the TCP checksum.

Finally, we add tests testing the new behavior.

Signed-off-by: Jarno Rajahalme <jrajahalme@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
lib/meta-flow.c
lib/nx-match.c
lib/odp-execute.c
lib/ofp-actions.c
ofproto/ofproto-dpif-xlate.c
ofproto/ofproto-dpif.c
tests/ofproto-dpif.at

index ddf0431..4342337 100644 (file)
@@ -274,11 +274,14 @@ mf_are_prereqs_ok(const struct mf_field *mf, const struct flow *flow)
         return is_ip_any(flow);
 
     case MFP_TCP:
-        return is_ip_any(flow) && flow->nw_proto == IPPROTO_TCP;
+        return is_ip_any(flow) && flow->nw_proto == IPPROTO_TCP
+            && !(flow->nw_frag & FLOW_NW_FRAG_LATER);
     case MFP_UDP:
-        return is_ip_any(flow) && flow->nw_proto == IPPROTO_UDP;
+        return is_ip_any(flow) && flow->nw_proto == IPPROTO_UDP
+            && !(flow->nw_frag & FLOW_NW_FRAG_LATER);
     case MFP_SCTP:
-        return is_ip_any(flow) && flow->nw_proto == IPPROTO_SCTP;
+        return is_ip_any(flow) && flow->nw_proto == IPPROTO_SCTP
+            && !(flow->nw_frag & FLOW_NW_FRAG_LATER);
     case MFP_ICMPV4:
         return is_icmpv4(flow);
     case MFP_ICMPV6:
@@ -324,6 +327,7 @@ mf_mask_field_and_prereqs(const struct mf_field *mf, struct flow *mask)
     case MFP_SCTP:
     case MFP_ICMPV4:
     case MFP_ICMPV6:
+        /* nw_frag always unwildcarded. */
         mask->nw_proto = 0xff;
         /* Fall through. */
     case MFP_ARP:
index e70eeac..bc6682d 100644 (file)
@@ -1357,7 +1357,7 @@ nxm_reg_move_check(const struct ofpact_reg_move *move, const struct flow *flow)
         return error;
     }
 
-    return mf_check_dst(&move->dst, NULL);
+    return mf_check_dst(&move->dst, flow);
 }
 \f
 /* nxm_execute_reg_move(). */
@@ -1372,12 +1372,18 @@ nxm_execute_reg_move(const struct ofpact_reg_move *move,
     mf_mask_field_and_prereqs(move->dst.field, &wc->masks);
     mf_mask_field_and_prereqs(move->src.field, &wc->masks);
 
-    mf_get_value(move->dst.field, flow, &dst_value);
-    mf_get_value(move->src.field, flow, &src_value);
-    bitwise_copy(&src_value, move->src.field->n_bytes, move->src.ofs,
-                 &dst_value, move->dst.field->n_bytes, move->dst.ofs,
-                 move->src.n_bits);
-    mf_set_flow_value(move->dst.field, &dst_value, flow);
+    /* A flow may wildcard nw_frag.  Do nothing if setting a transport
+     * header field on a packet that does not have them. */
+    if (mf_are_prereqs_ok(move->dst.field, flow)
+        && mf_are_prereqs_ok(move->src.field, flow)) {
+
+        mf_get_value(move->dst.field, flow, &dst_value);
+        mf_get_value(move->src.field, flow, &src_value);
+        bitwise_copy(&src_value, move->src.field->n_bytes, move->src.ofs,
+                     &dst_value, move->dst.field->n_bytes, move->dst.ofs,
+                     move->src.n_bits);
+        mf_set_flow_value(move->dst.field, &dst_value, flow);
+    }
 }
 
 void
index 230e6e1..1406fd8 100644 (file)
@@ -112,9 +112,11 @@ odp_set_tcp(struct ofpbuf *packet, const struct ovs_key_tcp *key,
 {
     struct tcp_header *th = ofpbuf_l4(packet);
 
-    packet_set_tcp_port(packet,
-                        key->tcp_src | (th->tcp_src & ~mask->tcp_src),
-                        key->tcp_dst | (th->tcp_dst & ~mask->tcp_dst));
+    if (OVS_LIKELY(th && ofpbuf_get_tcp_payload(packet))) {
+        packet_set_tcp_port(packet,
+                            key->tcp_src | (th->tcp_src & ~mask->tcp_src),
+                            key->tcp_dst | (th->tcp_dst & ~mask->tcp_dst));
+    }
 }
 
 static void
@@ -123,9 +125,11 @@ odp_set_udp(struct ofpbuf *packet, const struct ovs_key_udp *key,
 {
     struct udp_header *uh = ofpbuf_l4(packet);
 
-    packet_set_udp_port(packet,
-                        key->udp_src | (uh->udp_src & ~mask->udp_src),
-                        key->udp_dst | (uh->udp_dst & ~mask->udp_dst));
+    if (OVS_LIKELY(uh && ofpbuf_get_udp_payload(packet))) {
+        packet_set_udp_port(packet,
+                            key->udp_src | (uh->udp_src & ~mask->udp_src),
+                            key->udp_dst | (uh->udp_dst & ~mask->udp_dst));
+    }
 }
 
 static void
@@ -134,9 +138,11 @@ odp_set_sctp(struct ofpbuf *packet, const struct ovs_key_sctp *key,
 {
     struct sctp_header *sh = ofpbuf_l4(packet);
 
-    packet_set_sctp_port(packet,
-                         key->sctp_src | (sh->sctp_src & ~mask->sctp_src),
-                         key->sctp_dst | (sh->sctp_dst & ~mask->sctp_dst));
+    if (OVS_LIKELY(sh && ofpbuf_get_sctp_payload(packet))) {
+        packet_set_sctp_port(packet,
+                             key->sctp_src | (sh->sctp_src & ~mask->sctp_src),
+                             key->sctp_dst | (sh->sctp_dst & ~mask->sctp_dst));
+    }
 }
 
 static void
@@ -180,9 +186,6 @@ odp_execute_set_action(struct dpif_packet *packet, const struct nlattr *a)
     enum ovs_key_attr type = nl_attr_type(a);
     const struct ovs_key_ipv4 *ipv4_key;
     const struct ovs_key_ipv6 *ipv6_key;
-    const struct ovs_key_tcp *tcp_key;
-    const struct ovs_key_udp *udp_key;
-    const struct ovs_key_sctp *sctp_key;
     struct pkt_metadata *md = &packet->md;
 
     switch (type) {
@@ -218,21 +221,33 @@ odp_execute_set_action(struct dpif_packet *packet, const struct nlattr *a)
         break;
 
     case OVS_KEY_ATTR_TCP:
-        tcp_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_tcp));
-        packet_set_tcp_port(&packet->ofpbuf, tcp_key->tcp_src,
-                            tcp_key->tcp_dst);
+        if (OVS_LIKELY(ofpbuf_get_tcp_payload(&packet->ofpbuf))) {
+            const struct ovs_key_tcp *tcp_key
+                = nl_attr_get_unspec(a, sizeof(struct ovs_key_tcp));
+
+            packet_set_tcp_port(&packet->ofpbuf, tcp_key->tcp_src,
+                                tcp_key->tcp_dst);
+        }
         break;
 
     case OVS_KEY_ATTR_UDP:
-        udp_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_udp));
-        packet_set_udp_port(&packet->ofpbuf, udp_key->udp_src,
-                            udp_key->udp_dst);
+        if (OVS_LIKELY(ofpbuf_get_udp_payload(&packet->ofpbuf))) {
+            const struct ovs_key_udp *udp_key
+                = nl_attr_get_unspec(a, sizeof(struct ovs_key_udp));
+
+            packet_set_udp_port(&packet->ofpbuf, udp_key->udp_src,
+                                udp_key->udp_dst);
+        }
         break;
 
     case OVS_KEY_ATTR_SCTP:
-        sctp_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_sctp));
-        packet_set_sctp_port(&packet->ofpbuf, sctp_key->sctp_src,
-                             sctp_key->sctp_dst);
+        if (OVS_LIKELY(ofpbuf_get_sctp_payload(&packet->ofpbuf))) {
+            const struct ovs_key_sctp *sctp_key
+                = nl_attr_get_unspec(a, sizeof(struct ovs_key_sctp));
+
+            packet_set_sctp_port(&packet->ofpbuf, sctp_key->sctp_src,
+                                 sctp_key->sctp_dst);
+        }
         break;
 
     case OVS_KEY_ATTR_MPLS:
index 41c7622..33b419d 100644 (file)
@@ -5333,19 +5333,8 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
         return 0;
 
     case OFPACT_SET_L4_SRC_PORT:
-        if (!is_ip_any(flow) ||
-            (flow->nw_proto != IPPROTO_TCP && flow->nw_proto != IPPROTO_UDP
-             && flow->nw_proto != IPPROTO_SCTP)) {
-            inconsistent_match(usable_protocols);
-        }
-        /* Note on which transport protocol the port numbers are set.
-         * This allows this set action to be converted to an OF1.2 set field
-         * action. */
-        ofpact_get_SET_L4_SRC_PORT(a)->flow_ip_proto = flow->nw_proto;
-        return 0;
-
     case OFPACT_SET_L4_DST_PORT:
-        if (!is_ip_any(flow) ||
+        if (!is_ip_any(flow) || (flow->nw_frag & FLOW_NW_FRAG_LATER) ||
             (flow->nw_proto != IPPROTO_TCP && flow->nw_proto != IPPROTO_UDP
              && flow->nw_proto != IPPROTO_SCTP)) {
             inconsistent_match(usable_protocols);
@@ -5353,7 +5342,11 @@ ofpact_check__(enum ofputil_protocol *usable_protocols, struct ofpact *a,
         /* Note on which transport protocol the port numbers are set.
          * This allows this set action to be converted to an OF1.2 set field
          * action. */
-        ofpact_get_SET_L4_DST_PORT(a)->flow_ip_proto = flow->nw_proto;
+        if (a->type == OFPACT_SET_L4_SRC_PORT) {
+            ofpact_get_SET_L4_SRC_PORT(a)->flow_ip_proto = flow->nw_proto;
+        } else {
+            ofpact_get_SET_L4_DST_PORT(a)->flow_ip_proto = flow->nw_proto;
+        }
         return 0;
 
     case OFPACT_REG_MOVE:
index ba002ba..9a21f05 100644 (file)
@@ -3725,7 +3725,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_L4_SRC_PORT:
-            if (is_ip_any(flow)) {
+            if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
                 memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
                 memset(&wc->masks.tp_src, 0xff, sizeof wc->masks.tp_src);
                 flow->tp_src = htons(ofpact_get_SET_L4_SRC_PORT(a)->port);
@@ -3733,7 +3733,7 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             break;
 
         case OFPACT_SET_L4_DST_PORT:
-            if (is_ip_any(flow)) {
+            if (is_ip_any(flow) && !(flow->nw_frag & FLOW_NW_FRAG_LATER)) {
                 memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto);
                 memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst);
                 flow->tp_dst = htons(ofpact_get_SET_L4_DST_PORT(a)->port);
@@ -3780,10 +3780,13 @@ do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
                        && !eth_type_mpls(flow->dl_type)) {
                 break;
             }
-
+            /* A flow may wildcard nw_frag.  Do nothing if setting a trasport
+             * header field on a packet that does not have them. */
             mf_mask_field_and_prereqs(mf, &wc->masks);
-            mf_set_flow_value_masked(mf, &set_field->value, &set_field->mask,
-                                     flow);
+            if (mf_are_prereqs_ok(mf, flow)) {
+                mf_set_flow_value_masked(mf, &set_field->value,
+                                         &set_field->mask, flow);
+            }
             break;
 
         case OFPACT_STACK_PUSH:
index 40401ef..9c6d386 100644 (file)
@@ -4643,7 +4643,7 @@ ofproto_unixctl_trace_actions(struct unixctl_conn *conn, int argc,
 
     /* Do the same checks as handle_packet_out() in ofproto.c.
      *
-     * We pass a 'table_id' of 0 to ofproto_check_ofpacts(), which isn't
+     * We pass a 'table_id' of 0 to ofpacts_check(), which isn't
      * strictly correct because these actions aren't in any table, but it's OK
      * because it 'table_id' is used only to check goto_table instructions, but
      * packet-outs take a list of actions and therefore it can't include
index 3a33926..9d8efdc 100644 (file)
@@ -3324,16 +3324,15 @@ OFPST_FLOW reply (OF1.2):
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
-AT_SETUP([ofproto-dpif - fragment handling])
+AT_SETUP([ofproto-dpif - fragment handling - trace])
 OVS_VSWITCHD_START
 ADD_OF_PORTS([br0], [1], [2], [3], [4], [5], [6], [90])
 AT_DATA([flows.txt], [dnl
 priority=75 tcp ip_frag=no    tp_dst=80 actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:1
 priority=75 tcp ip_frag=first tp_dst=80 actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:2
-priority=75 tcp ip_frag=later tp_dst=80 actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:3
 priority=50 tcp ip_frag=no              actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:4
 priority=50 tcp ip_frag=first           actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:5
-priority=50 tcp ip_frag=later           actions=move:OXM_OF_TCP_DST[[]]->OXM_OF_TCP_SRC[[]],output:6
+priority=50 tcp ip_frag=later           actions=output:6
 ])
 AT_CHECK([ovs-ofctl replace-flows br0 flows.txt])
 
@@ -3363,9 +3362,7 @@ do
     if test $mode = drop && test $type != no; then
         echo 'Packets dropped because they are IP fragments and the fragment handling mode is "drop".' >> expout
         echo "Datapath actions: $exp_output" >> expout
-    elif test $mode = normal && test $type = later; then
-        echo "Datapath actions: $exp_output" >> expout
-    elif test $mode = nx-match && test $type = later; then
+    elif test $type = later; then
         echo "Datapath actions: $exp_output" >> expout
     else
         echo "Datapath actions: set(tcp(src=80,dst=80)),$exp_output" >> expout
@@ -3376,6 +3373,177 @@ done
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - fragment handling - upcall])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2], [3], [4], [5], [6], [90])
+AT_DATA([flows.txt], [dnl
+priority=75 tcp ip_frag=no    tp_dst=80 actions=set_field:81->tcp_dst,output:1
+priority=75 tcp ip_frag=first tp_dst=80 actions=set_field:81->tcp_dst,output:2
+priority=50 tcp ip_frag=no              actions=set_field:81->tcp_dst,output:4
+priority=50 tcp ip_frag=first           actions=set_field:81->tcp_dst,output:5
+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,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)"
+
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
+
+mode=normal
+
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for type in no first later; do
+  eval flow=\$${type}_flow
+  printf "\n%s\n" "----$mode $type-----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$flow"], [0], [stdout])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(dst=80), packets:0, bytes:0, used:never, actions:set(tcp(dst=81)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=first),tcp(dst=80), packets:0, bytes:0, used:never, actions:set(tcp(dst=81)),5
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=later), packets:0, bytes:0, used:never, actions:6
+])
+
+mode=drop
+
+AT_CHECK([ovs-appctl dpctl/del-flows], [0])
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for type in no first later; do
+  eval flow=\$${type}_flow
+  printf "\n%s\n" "----$mode $type-----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$flow"], [0], [stdout])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(dst=80), packets:0, bytes:0, used:never, actions:set(tcp(dst=81)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(frag=first), packets:0, bytes:0, used:never, actions:drop
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(frag=later), packets:0, bytes:0, used:never, actions:drop
+])
+
+mode=nx-match
+
+AT_CHECK([ovs-appctl dpctl/del-flows], [0])
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for type in no first later; do
+  eval flow=\$${type}_flow
+  printf "\n%s\n" "----$mode $type-----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$flow"], [0], [stdout])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(dst=80), packets:0, bytes:0, used:never, actions:set(tcp(dst=81)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=first),tcp(dst=80), packets:0, bytes:0, used:never, actions:set(tcp(dst=81)),2
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=later), packets:0, bytes:0, used:never, actions:6
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([ofproto-dpif - fragment handling - actions])
+OVS_VSWITCHD_START
+ADD_OF_PORTS([br0], [1], [2], [3], [4], [5], [6], [90])
+
+AT_CHECK([ovs-ofctl add-flow br0 "tcp,ip_frag=later actions=move:OXM_OF_TCP_DST[[0..7]]->OXM_OF_TCP_SRC[[0..7]],output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+source field tcp_dst lacks correct prerequisites
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl -O OpenFlow15 add-flow br0 "tcp,ip_frag=later actions=move:OXM_OF_PKT_REG0[[0..7]]->OXM_OF_TCP_SRC[[0..7]],output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+destination field tcp_src lacks correct prerequisites
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 "udp,ip_frag=later actions=set_field:8888->udp_src,output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+set_field udp_src lacks correct prerequisities
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 "udp,ip_frag=later actions=load:8888->NXM_OF_UDP_DST[[]],output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+set_field udp_dst lacks correct prerequisities
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 "sctp,ip_frag=later actions=set_field:8888->sctp_src,output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+set_field sctp_src lacks correct prerequisities
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 "sctp,ip_frag=later actions=set_field:8888->sctp_dst,output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+set_field sctp_dst lacks correct prerequisities
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_CHECK([ovs-ofctl add-flow br0 "tcp,ip_frag=later actions=learn(table=1,hard_timeout=60,eth_type=0x800,nw_proto=6,NXM_OF_IP_SRC[[]]=NXM_OF_IP_DST[[]],NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],output:NXM_NX_REG0[[0..15]]),output:1"], [1], [], [stderr])
+AT_CHECK([tail -2 stderr | sed 's/^.*|WARN|//'], [0], [dnl
+source field tcp_dst lacks correct prerequisites
+ovs-ofctl: actions are invalid with specified match (OFPBAC_MATCH_INCONSISTENT)
+])
+
+AT_DATA([flows.txt], [dnl
+priority=75 tcp actions=load:42->OXM_OF_TCP_SRC[[0..7]],output:1
+])
+AT_CHECK([ovs-ofctl -O OpenFlow12 replace-flows br0 flows.txt])
+
+AT_CHECK([ovs-appctl vlog/set dpif:dbg dpif_netdev:dbg])
+
+mode=normal
+
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for frag in 4000 6000 6008 4010; do
+  printf "\n%s\n" "----$mode $frag-----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "0021853763af 0026b98cb0f9 0800 4500 003c 2e24 $frag 40 06 465d ac11370d ac11370b 828b 0016 751e267b 00000000 a002 16d0 1736 0000 02 04 05 b4 04 02 08 0a 2d 25 08 5f 00 00 00 00 01 03 03 07"])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(src=33419), packets:0, bytes:0, used:never, actions:set(tcp(src=33322)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=first),tcp(src=33419), packets:0, bytes:0, used:never, actions:set(tcp(src=33322)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=later), packets:1, bytes:74, used:0.001s, actions:1
+])
+
+AT_CHECK([ovs-appctl dpctl/del-flows], [0])
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for frag in 4000 6000 6008 4010; do
+  printf "\n%s\n" "----$mode $frag truncated transport header -----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "0021853763af 0026b98cb0f9 0800 4500 0018 2e24 $frag 40 06 465d ac11370d ac11370b 828b 0016"])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(src=0), packets:0, bytes:0, used:never, actions:set(tcp(src=42)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=first),tcp(src=0), packets:0, bytes:0, used:never, actions:set(tcp(src=42)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=later), packets:1, bytes:60, used:0.001s, actions:1
+])
+
+AT_CHECK([ovs-appctl dpctl/del-flows], [0])
+AT_CHECK([ovs-ofctl set-frags br0 $mode])
+for frag in 4000 6000 6001 4002; do
+  printf "\n%s\n" "----$mode $frag missing transport header-----"
+
+  AT_CHECK([ovs-appctl netdev-dummy/receive p90 "0021853763af 0026b98cb0f9 0800 4500 0014 2e24 $frag 40 06 465d ac11370d ac11370b"])
+done
+
+AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=no),tcp(src=0), packets:0, bytes:0, used:never, actions:set(tcp(src=42)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=first),tcp(src=0), packets:0, bytes:0, used:never, actions:set(tcp(src=42)),1
+recirc_id(0),in_port(90),eth_type(0x0800),ipv4(proto=6,frag=later), packets:1, bytes:60, used:0.001s, actions:1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_SETUP([ofproto-dpif - exit])
 OVS_VSWITCHD_START
 ADD_OF_PORTS([br0], [1], [2], [3], [10], [11], [12], [13], [14])