diff mbox series

[ovs-dev,RFC,v4,3/4] ovn: Support OVS action 'check_pkt_larger' in OVN

Message ID 20190320112635.23048-1-nusiddiq@redhat.com
State Superseded
Headers show
Series Address MTU issue for larger packets in OVN | expand

Commit Message

Numan Siddique March 20, 2019, 11:26 a.m. UTC
From: Numan Siddique <nusiddiq@redhat.com>

Previous commit added a new OVS action 'check_pkt_larger'. This
patch supports that action in OVN. The syntax to use this would be

reg0[0] = check_pkt_larger(LEN)

Upcoming commit will make use of this action in ovn-northd and
will generate an ICMPv4 packet if the packet length is greater than
the specified length.

Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
---
 include/ovn/actions.h     | 10 +++++++-
 ovn/controller/pinctrl.c  | 40 +++++++++++++++++++++++++++++
 ovn/lib/actions.c         | 53 +++++++++++++++++++++++++++++++++++++++
 ovn/ovn-sb.xml            | 21 ++++++++++++++++
 ovn/utilities/ovn-trace.c |  4 ++-
 tests/ovn.at              | 10 ++++++++
 6 files changed, 136 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/include/ovn/actions.h b/include/ovn/actions.h
index 095b60df0..6571e2aee 100644
--- a/include/ovn/actions.h
+++ b/include/ovn/actions.h
@@ -81,7 +81,8 @@  struct ovn_extend_table;
     OVNACT(PUT_ND_RA_OPTS,    ovnact_put_opts)        \
     OVNACT(ND_NS,             ovnact_nest)            \
     OVNACT(SET_METER,         ovnact_set_meter)       \
-    OVNACT(OVNFIELD_LOAD,     ovnact_load)
+    OVNACT(OVNFIELD_LOAD,     ovnact_load)            \
+    OVNACT(CHECK_PKT_LARGER,  ovnact_check_pkt_larger)
 
 /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
 enum OVS_PACKED_ENUM ovnact_type {
@@ -311,6 +312,13 @@  struct ovnact_set_meter {
     uint64_t burst;                  /* burst rate field, in kbps. */
 };
 
+/* OVNACT_CHECK_IP_PKT_LARGER. */
+struct ovnact_check_pkt_larger {
+    struct ovnact ovnact;
+    uint16_t pkt_len;
+    struct expr_field dst;      /* 1-bit destination field. */
+};
+
 /* Internal use by the helpers below. */
 void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
 void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
index fda594109..f020acf01 100644
--- a/ovn/controller/pinctrl.c
+++ b/ovn/controller/pinctrl.c
@@ -435,6 +435,16 @@  pinctrl_handle_icmp(const struct flow *ip_flow, struct dp_packet *pkt_in,
     eh->eth_src = ip_flow->dl_src;
 
     if (get_dl_type(ip_flow) == htons(ETH_TYPE_IP)) {
+        struct ip_header *in_ip = dp_packet_l3(pkt_in);
+        uint16_t in_ip_len = ntohs(in_ip->ip_tot_len);
+        if (in_ip_len < IP_HEADER_LEN) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl,
+                        "ICMP action on IP packet with invalid length (%u)",
+                        in_ip_len);
+            return;
+        }
+
         struct ip_header *nh = dp_packet_put_zeros(&packet, sizeof *nh);
 
         eh->eth_type = htons(ETH_TYPE_IP);
@@ -452,6 +462,8 @@  pinctrl_handle_icmp(const struct flow *ip_flow, struct dp_packet *pkt_in,
         packet_set_icmp(&packet, ICMP4_DST_UNREACH, 1);
         uint8_t *n_ovnfield_acts = ofpbuf_try_pull(userdata,
                                                    sizeof *n_ovnfield_acts);
+        bool include_orig_ip_datagram = false;
+
         if (n_ovnfield_acts && *n_ovnfield_acts) {
             for (uint8_t i = 0; i < *n_ovnfield_acts; i++) {
                  struct ovnfield_act_header *oah =
@@ -465,12 +477,40 @@  pinctrl_handle_icmp(const struct flow *ip_flow, struct dp_packet *pkt_in,
                      ovs_be16 *mtu = ofpbuf_try_pull(userdata, sizeof *mtu);
                      if (mtu) {
                          ih->icmp_fields.frag.mtu = *mtu;
+                         include_orig_ip_datagram = true;
                      }
                      break;
                  }
                  }
             }
         }
+
+        if (include_orig_ip_datagram) {
+            /* RFC 1122: 3.2.2	MUST send at least the IP header and 8 bytes
+             * of header. MAY send more.
+             * RFC says return as much as we can without exceeding 576
+             * bytes.
+             * So, lets return as much as we can. */
+
+            /* Calculate available room to include the original IP + data. */
+            nh = dp_packet_l3(&packet);
+            uint16_t room = 576 - (sizeof *eh + ntohs(nh->ip_tot_len));
+            if (in_ip_len > room) {
+                in_ip_len = room;
+            }
+            dp_packet_put(&packet, in_ip, in_ip_len);
+
+            /* dp_packet_put may reallocate the buffer. Get the l3 and l4
+             * header pointers again. */
+            nh = dp_packet_l3(&packet);
+            ih = dp_packet_l4(&packet);
+            uint16_t ip_total_len = ntohs(nh->ip_tot_len) + in_ip_len;
+            nh->ip_tot_len = htons(ip_total_len);
+            ih->icmp_csum = 0;
+            ih->icmp_csum = csum(ih, sizeof *ih + in_ip_len);
+            nh->ip_csum = 0;
+            nh->ip_csum = csum(nh, sizeof *nh);
+        }
     } else {
         struct ip6_hdr *nh = dp_packet_put_zeros(&packet, sizeof *nh);
         struct icmp6_error_header *ih;
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
index 36d21c400..a5830d819 100644
--- a/ovn/lib/actions.c
+++ b/ovn/lib/actions.c
@@ -2372,6 +2372,55 @@  encode_OVNFIELD_LOAD(const struct ovnact_load *load,
     }
 }
 
+static void
+parse_check_pkt_larger(struct action_context *ctx,
+                       const struct expr_field *dst,
+                       struct ovnact_check_pkt_larger *cipl)
+{
+    int pkt_len;
+
+    lexer_get(ctx->lexer); /* Skip check_pkt_len. */
+    if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)
+        || !lexer_get_int(ctx->lexer, &pkt_len)
+        || !lexer_force_match(ctx->lexer, LEX_T_RPAREN)) {
+        return;
+    }
+
+    /* Validate that the destination is a 1-bit, modifiable field. */
+    char *error = expr_type_check(dst, 1, true);
+    if (error) {
+        lexer_error(ctx->lexer, "%s", error);
+        free(error);
+        return;
+    }
+    cipl->dst = *dst;
+    cipl->pkt_len = pkt_len;
+}
+
+static void
+format_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl,
+                        struct ds *s)
+{
+    expr_field_format(&cipl->dst, s);
+    ds_put_format(s, " = check_pkt_larger(%d);", cipl->pkt_len);
+}
+
+static void
+encode_CHECK_PKT_LARGER(const struct ovnact_check_pkt_larger *cipl,
+                        const struct ovnact_encode_params *ep OVS_UNUSED,
+                        struct ofpbuf *ofpacts)
+{
+    struct ofpact_check_pkt_larger *check_pkt_larger =
+        ofpact_put_CHECK_PKT_LARGER(ofpacts);
+    check_pkt_larger->pkt_len = cipl->pkt_len;
+    check_pkt_larger->dst = expr_resolve_field(&cipl->dst);
+}
+
+static void
+ovnact_check_pkt_larger_free(struct ovnact_check_pkt_larger *cipl OVS_UNUSED)
+{
+}
+
 /* Parses an assignment or exchange or put_dhcp_opts action. */
 static void
 parse_set_action(struct action_context *ctx)
@@ -2401,6 +2450,10 @@  parse_set_action(struct action_context *ctx)
                 && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
             parse_put_nd_ra_opts(ctx, &lhs,
                                  ovnact_put_PUT_ND_RA_OPTS(ctx->ovnacts));
+        } else if (!strcmp(ctx->lexer->token.s, "check_pkt_larger")
+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+            parse_check_pkt_larger(ctx, &lhs,
+                                   ovnact_put_CHECK_PKT_LARGER(ctx->ovnacts));
         } else {
             parse_assignment_action(ctx, false, &lhs);
         }
diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
index 977cb8bc0..4e05a5b67 100644
--- a/ovn/ovn-sb.xml
+++ b/ovn/ovn-sb.xml
@@ -1766,6 +1766,27 @@ 
 
           <p><b>Example:</b> <code>set_meter(100, 1000);</code></p>
         </dd>
+
+        <dt><code><var>R</var> = check_pkt_larger(<var>L</var>)</code></dt>
+        <dd>
+          <p>
+            <b>Parameters</b>: packet length <var>L</var> to check for
+            in bytes.
+          </p>
+
+          <p>
+            <b>Result</b>: stored to a 1-bit subfield <var>R</var>.
+          </p>
+
+          <p>
+            This is a logical equivalent of the OpenFlow
+            <code>check_pkt_larger</code> action. If the packet is larger
+            than the length specified in <var>L</var>, it stores 1 in the
+            subfield <var>R</var>.
+          </p>
+
+          <p><b>Example: </b><code>reg0[6] = check_pkt_larger(1000);</code></p>
+        </dd>
       </dl>
 
       <dl>
diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
index b00f53a5c..9fc397d7d 100644
--- a/ovn/utilities/ovn-trace.c
+++ b/ovn/utilities/ovn-trace.c
@@ -2109,8 +2109,10 @@  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
 
         case OVNACT_OVNFIELD_LOAD:
             break;
-        }
 
+        case OVNACT_CHECK_PKT_LARGER:
+            break;
+        }
     }
     ds_destroy(&s);
 }
diff --git a/tests/ovn.at b/tests/ovn.at
index 7f096699a..c13527566 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -1330,6 +1330,16 @@  ip4.src <-> ip6.src[0..31];
     encodes as push:NXM_NX_IPV6_SRC[0..31],push:NXM_OF_IP_SRC[],pop:NXM_NX_IPV6_SRC[0..31],pop:NXM_OF_IP_SRC[]
     has prereqs eth.type == 0x800 && eth.type == 0x86dd
 
+# check_pkt_larger
+reg0[0] = check_pkt_larger(1500);
+    encodes as check_pkt_larger(1500)->NXM_NX_XXREG0[96]
+
+reg0 = check_pkt_larger(1500);
+    Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
+
+reg0 = check_pkt_larger(foo);
+    Syntax error at `foo' expecting `;'.
+
 # Miscellaneous negative tests.
 ;
     Syntax error at `;'.