nicira-ext: Support matching IPv6 traffic.
[sliver-openvswitch.git] / lib / ofp-parse.c
index 6b53f26..e77453e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Nicira Networks.
+ * Copyright (c) 2010, 2011 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 #include "ofp-parse.h"
 
+#include <ctype.h>
 #include <errno.h>
 #include <stdlib.h>
 
 #include "byte-order.h"
 #include "dynamic-string.h"
 #include "netdev.h"
+#include "multipath.h"
+#include "nx-match.h"
 #include "ofp-util.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
@@ -125,6 +128,75 @@ str_to_ip(const char *str_, ovs_be32 *ip, ovs_be32 *maskp)
     free(str);
 }
 
+static void
+str_to_tun_id(const char *str, ovs_be64 *tun_idp, ovs_be64 *maskp)
+{
+    uint64_t tun_id, mask;
+    char *tail;
+
+    errno = 0;
+    tun_id = strtoull(str, &tail, 0);
+    if (errno || (*tail != '\0' && *tail != '/')) {
+        goto error;
+    }
+
+    if (*tail == '/') {
+        mask = strtoull(tail + 1, &tail, 0);
+        if (errno || *tail != '\0') {
+            goto error;
+        }
+    } else {
+        mask = UINT64_MAX;
+    }
+
+    *tun_idp = htonll(tun_id);
+    *maskp = htonll(mask);
+    return;
+
+error:
+    ovs_fatal(0, "%s: bad syntax for tunnel id", str);
+}
+
+static void
+str_to_ipv6(const char *str_, struct in6_addr *addrp, struct in6_addr *maskp)
+{
+    char *str = xstrdup(str_);
+    char *save_ptr = NULL;
+    const char *name, *netmask;
+    struct in6_addr addr, mask;
+    int retval;
+
+    name = strtok_r(str, "/", &save_ptr);
+    retval = name ? lookup_ipv6(name, &addr) : EINVAL;
+    if (retval) {
+        ovs_fatal(0, "%s: could not convert to IPv6 address", str);
+    }
+
+    netmask = strtok_r(NULL, "/", &save_ptr);
+    if (netmask) {
+        int prefix = atoi(netmask);
+        if (prefix <= 0 || prefix > 128) {
+            ovs_fatal(0, "%s: network prefix bits not between 1 and 128",
+                      str);
+        } else {
+            mask = ipv6_create_mask(prefix);
+        }
+    } else {
+        mask = in6addr_exact;
+    }
+    *addrp = ipv6_addr_bitand(&addr, &mask);
+
+    if (maskp) {
+        *maskp = mask;
+    } else {
+        if (!ipv6_mask_is_exact(&mask)) {
+            ovs_fatal(0, "%s: netmask not allowed here", str_);
+        }
+    }
+
+    free(str);
+}
+
 static void *
 put_action(struct ofpbuf *b, size_t size, uint16_t type)
 {
@@ -192,26 +264,66 @@ parse_port_name(const char *name, uint16_t *port)
 static void
 str_to_action(char *str, struct ofpbuf *b)
 {
-    char *act, *arg;
-    char *saveptr = NULL;
     bool drop = false;
     int n_actions;
+    char *pos;
 
-    for (act = strtok_r(str, ", \t\r\n", &saveptr), n_actions = 0; act;
-         act = strtok_r(NULL, ", \t\r\n", &saveptr), n_actions++)
-    {
+    pos = str;
+    n_actions = 0;
+    for (;;) {
+        char *act, *arg;
+        size_t actlen;
         uint16_t port;
 
+        pos += strspn(pos, ", \t\r\n");
+        if (*pos == '\0') {
+            break;
+        }
+
         if (drop) {
             ovs_fatal(0, "Drop actions must not be followed by other actions");
         }
 
-        /* Arguments are separated by colons */
-        arg = strchr(act, ':');
-        if (arg) {
-            *arg = '\0';
-            arg++;
+        act = pos;
+        actlen = strcspn(pos, ":(, \t\r\n");
+        if (act[actlen] == ':') {
+            /* The argument can be separated by a colon. */
+            size_t arglen;
+
+            arg = act + actlen + 1;
+            arglen = strcspn(arg, ", \t\r\n");
+            pos = arg + arglen + (arg[arglen] != '\0');
+            arg[arglen] = '\0';
+        } else if (act[actlen] == '(') {
+            /* The argument can be surrounded by balanced parentheses.  The
+             * outermost set of parentheses is removed. */
+            int level = 1;
+            size_t arglen;
+
+            arg = act + actlen + 1;
+            for (arglen = 0; level > 0; arglen++) {
+                switch (arg[arglen]) {
+                case '\0':
+                    ovs_fatal(0, "unbalanced parentheses in argument to %s "
+                              "action", act);
+
+                case '(':
+                    level++;
+                    break;
+
+                case ')':
+                    level--;
+                    break;
+                }
+            }
+            arg[arglen - 1] = '\0';
+            pos = arg + arglen;
+        } else {
+            /* There might be no argument at all. */
+            arg = NULL;
+            pos = act + actlen + (act[actlen] != '\0');
         }
+        act[actlen] = '\0';
 
         if (!strcasecmp(act, "mod_vlan_vid")) {
             struct ofp_action_vlan_vid *va;
@@ -255,12 +367,22 @@ str_to_action(char *str, struct ofpbuf *b)
             nar->vendor = htonl(NX_VENDOR_ID);
             nar->subtype = htons(NXAST_RESUBMIT);
             nar->in_port = htons(str_to_u32(arg));
-        } else if (!strcasecmp(act, "set_tunnel")) {
-            struct nx_action_set_tunnel *nast;
-            nast = put_action(b, sizeof *nast, OFPAT_VENDOR);
-            nast->vendor = htonl(NX_VENDOR_ID);
-            nast->subtype = htons(NXAST_SET_TUNNEL);
-            nast->tun_id = htonl(str_to_u32(arg));
+        } else if (!strcasecmp(act, "set_tunnel")
+                   || !strcasecmp(act, "set_tunnel64")) {
+            uint64_t tun_id = str_to_u64(arg);
+            if (!strcasecmp(act, "set_tunnel64") || tun_id > UINT32_MAX) {
+                struct nx_action_set_tunnel64 *nast64;
+                nast64 = put_action(b, sizeof *nast64, OFPAT_VENDOR);
+                nast64->vendor = htonl(NX_VENDOR_ID);
+                nast64->subtype = htons(NXAST_SET_TUNNEL64);
+                nast64->tun_id = htonll(tun_id);
+            } else {
+                struct nx_action_set_tunnel *nast;
+                nast = put_action(b, sizeof *nast, OFPAT_VENDOR);
+                nast->vendor = htonl(NX_VENDOR_ID);
+                nast->subtype = htons(NXAST_SET_TUNNEL);
+                nast->tun_id = htonl(tun_id);
+            }
         } else if (!strcasecmp(act, "drop_spoofed_arp")) {
             struct nx_action_header *nah;
             nah = put_action(b, sizeof *nah, OFPAT_VENDOR);
@@ -289,8 +411,8 @@ str_to_action(char *str, struct ofpbuf *b)
 
             b->size -= sizeof nan->note;
             while (arg && *arg != '\0') {
-                int high, low;
                 uint8_t byte;
+                bool ok;
 
                 if (*arg == '.') {
                     arg++;
@@ -299,16 +421,13 @@ str_to_action(char *str, struct ofpbuf *b)
                     break;
                 }
 
-                high = hexit_value(*arg++);
-                if (high >= 0) {
-                    low = hexit_value(*arg++);
-                }
-                if (high < 0 || low < 0) {
+                byte = hexits_value(arg, 2, &ok);
+                if (!ok) {
                     ovs_fatal(0, "bad hex digit in `note' argument");
                 }
-
-                byte = high * 16 + low;
                 ofpbuf_put(b, &byte, 1);
+
+                arg += 2;
             }
 
             len = b->size - start_ofs;
@@ -317,6 +436,18 @@ str_to_action(char *str, struct ofpbuf *b)
                 ofpbuf_put_zeros(b, OFP_ACTION_ALIGN - remainder);
             }
             nan->len = htons(b->size - start_ofs);
+        } else if (!strcasecmp(act, "move")) {
+            struct nx_action_reg_move *move;
+            move = ofpbuf_put_uninit(b, sizeof *move);
+            nxm_parse_reg_move(move, arg);
+        } else if (!strcasecmp(act, "load")) {
+            struct nx_action_reg_load *load;
+            load = ofpbuf_put_uninit(b, sizeof *load);
+            nxm_parse_reg_load(load, arg);
+        } else if (!strcasecmp(act, "multipath")) {
+            struct nx_action_multipath *nam;
+            nam = ofpbuf_put_uninit(b, sizeof *nam);
+            multipath_parse(nam, arg);
         } else if (!strcasecmp(act, "output")) {
             put_output_action(b, str_to_u32(arg));
         } else if (!strcasecmp(act, "enqueue")) {
@@ -353,6 +484,7 @@ str_to_action(char *str, struct ofpbuf *b)
         } else {
             ovs_fatal(0, "Unknown action: %s", act);
         }
+        n_actions++;
     }
 }
 
@@ -368,9 +500,14 @@ parse_protocol(const char *name, const struct protocol **p_out)
     static const struct protocol protocols[] = {
         { "ip", ETH_TYPE_IP, 0 },
         { "arp", ETH_TYPE_ARP, 0 },
-        { "icmp", ETH_TYPE_IP, IP_TYPE_ICMP },
-        { "tcp", ETH_TYPE_IP, IP_TYPE_TCP },
-        { "udp", ETH_TYPE_IP, IP_TYPE_UDP },
+        { "icmp", ETH_TYPE_IP, IPPROTO_ICMP },
+        { "tcp", ETH_TYPE_IP, IPPROTO_TCP },
+        { "udp", ETH_TYPE_IP, IPPROTO_UDP },
+        { "ipv6", ETH_TYPE_IPV6, 0 },
+        { "ip6", ETH_TYPE_IPV6, 0 },
+        { "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
+        { "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
+        { "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
     };
     const struct protocol *p;
 
@@ -385,20 +522,25 @@ parse_protocol(const char *name, const struct protocol **p_out)
 }
 
 #define FIELDS                                              \
-    FIELD(F_IN_PORT,     "in_port",     OFPFW_IN_PORT)      \
-    FIELD(F_DL_VLAN,     "dl_vlan",     OFPFW_DL_VLAN)      \
-    FIELD(F_DL_VLAN_PCP, "dl_vlan_pcp", OFPFW_DL_VLAN_PCP)  \
-    FIELD(F_DL_SRC,      "dl_src",      OFPFW_DL_SRC)       \
-    FIELD(F_DL_DST,      "dl_dst",      OFPFW_DL_DST)       \
-    FIELD(F_DL_TYPE,     "dl_type",     OFPFW_DL_TYPE)      \
+    FIELD(F_TUN_ID,      "tun_id",      0)                  \
+    FIELD(F_IN_PORT,     "in_port",     FWW_IN_PORT)        \
+    FIELD(F_DL_VLAN,     "dl_vlan",     0)                  \
+    FIELD(F_DL_VLAN_PCP, "dl_vlan_pcp", 0)                  \
+    FIELD(F_DL_SRC,      "dl_src",      FWW_DL_SRC)         \
+    FIELD(F_DL_DST,      "dl_dst",      FWW_DL_DST)         \
+    FIELD(F_DL_TYPE,     "dl_type",     FWW_DL_TYPE)        \
     FIELD(F_NW_SRC,      "nw_src",      0)                  \
     FIELD(F_NW_DST,      "nw_dst",      0)                  \
-    FIELD(F_NW_PROTO,    "nw_proto",    OFPFW_NW_PROTO)     \
-    FIELD(F_NW_TOS,      "nw_tos",      OFPFW_NW_TOS)       \
-    FIELD(F_TP_SRC,      "tp_src",      OFPFW_TP_SRC)       \
-    FIELD(F_TP_DST,      "tp_dst",      OFPFW_TP_DST)       \
-    FIELD(F_ICMP_TYPE,   "icmp_type",   OFPFW_ICMP_TYPE)    \
-    FIELD(F_ICMP_CODE,   "icmp_code",   OFPFW_ICMP_CODE)
+    FIELD(F_NW_PROTO,    "nw_proto",    FWW_NW_PROTO)       \
+    FIELD(F_NW_TOS,      "nw_tos",      FWW_NW_TOS)         \
+    FIELD(F_TP_SRC,      "tp_src",      FWW_TP_SRC)         \
+    FIELD(F_TP_DST,      "tp_dst",      FWW_TP_DST)         \
+    FIELD(F_ICMP_TYPE,   "icmp_type",   FWW_TP_SRC)         \
+    FIELD(F_ICMP_CODE,   "icmp_code",   FWW_TP_DST)         \
+    FIELD(F_ARP_SHA,     "arp_sha",     FWW_ARP_SHA)        \
+    FIELD(F_ARP_THA,     "arp_tha",     FWW_ARP_THA)        \
+    FIELD(F_IPV6_SRC,    "ipv6_src",    0)                  \
+    FIELD(F_IPV6_DST,    "ipv6_dst",    0)
 
 enum field_index {
 #define FIELD(ENUM, NAME, WILDCARD) ENUM,
@@ -410,7 +552,7 @@ enum field_index {
 struct field {
     enum field_index index;
     const char *name;
-    uint32_t wildcard;
+    flow_wildcards_t wildcard;  /* FWW_* bit. */
 };
 
 static bool
@@ -438,10 +580,17 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
                   const char *value)
 {
     uint8_t mac[ETH_ADDR_LEN];
+    ovs_be64 tun_id, tun_mask;
     ovs_be32 ip, mask;
+    struct in6_addr ipv6, ipv6_mask;
     uint16_t port_no;
 
     switch (index) {
+    case F_TUN_ID:
+        str_to_tun_id(value, &tun_id, &tun_mask);
+        cls_rule_set_tun_id_masked(rule, tun_id, tun_mask);
+        break;
+
     case F_IN_PORT:
         if (!parse_port_name(value, &port_no)) {
             port_no = atoi(value);
@@ -508,26 +657,70 @@ parse_field_value(struct cls_rule *rule, enum field_index index,
         cls_rule_set_icmp_code(rule, str_to_u32(value));
         break;
 
+    case F_ARP_SHA:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_sha(rule, mac);
+        break;
+
+    case F_ARP_THA:
+        str_to_mac(value, mac);
+        cls_rule_set_arp_tha(rule, mac);
+        break;
+
+    case F_IPV6_SRC:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_src_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
+    case F_IPV6_DST:
+        str_to_ipv6(value, &ipv6, &ipv6_mask);
+        cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask);
+        break;
+
     case N_FIELDS:
         NOT_REACHED();
     }
 }
 
+static void
+parse_reg_value(struct cls_rule *rule, int reg_idx, const char *value)
+{
+    uint32_t reg_value, reg_mask;
+
+    if (!strcmp(value, "ANY") || !strcmp(value, "*")) {
+        cls_rule_set_reg_masked(rule, reg_idx, 0, 0);
+    } else if (sscanf(value, "%"SCNi32"/%"SCNi32,
+                      &reg_value, &reg_mask) == 2) {
+        cls_rule_set_reg_masked(rule, reg_idx, reg_value, reg_mask);
+    } else if (sscanf(value, "%"SCNi32, &reg_value)) {
+        cls_rule_set_reg(rule, reg_idx, reg_value);
+    } else {
+        ovs_fatal(0, "register fields must take the form <value> "
+                  "or <value>/<mask>");
+    }
+}
+
 /* Convert 'string' (as described in the Flow Syntax section of the ovs-ofctl
  * man page) into 'pf'.  If 'actions' is specified, an action must be in
  * 'string' and may be expanded or reallocated. */
-void
-parse_ofp_str(struct parsed_flow *pf, struct ofpbuf *actions, char *string)
+static void
+parse_ofp_str(struct flow_mod *fm, uint8_t *table_idx,
+              struct ofpbuf *actions, char *string)
 {
     char *save_ptr = NULL;
     char *name;
 
-    cls_rule_init_catchall(&pf->rule, OFP_DEFAULT_PRIORITY);
-    pf->table_idx = 0xff;
-    pf->out_port = OFPP_NONE;
-    pf->idle_timeout = OFP_FLOW_PERMANENT;
-    pf->hard_timeout = OFP_FLOW_PERMANENT;
-    pf->cookie = 0;
+    if (table_idx) {
+        *table_idx = 0xff;
+    }
+    cls_rule_init_catchall(&fm->cr, OFP_DEFAULT_PRIORITY);
+    fm->cookie = htonll(0);
+    fm->command = UINT16_MAX;
+    fm->idle_timeout = OFP_FLOW_PERMANENT;
+    fm->hard_timeout = OFP_FLOW_PERMANENT;
+    fm->buffer_id = UINT32_MAX;
+    fm->out_port = OFPP_NONE;
+    fm->flags = 0;
     if (actions) {
         char *act_str = strstr(string, "action");
         if (!act_str) {
@@ -543,15 +736,20 @@ parse_ofp_str(struct parsed_flow *pf, struct ofpbuf *actions, char *string)
         act_str++;
 
         str_to_action(act_str, actions);
+        fm->actions = actions->data;
+        fm->n_actions = actions->size / sizeof(union ofp_action);
+    } else {
+        fm->actions = NULL;
+        fm->n_actions = 0;
     }
     for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
          name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
         const struct protocol *p;
 
         if (parse_protocol(name, &p)) {
-            cls_rule_set_dl_type(&pf->rule, htons(p->dl_type));
+            cls_rule_set_dl_type(&fm->cr, htons(p->dl_type));
             if (p->nw_proto) {
-                cls_rule_set_nw_proto(&pf->rule, p->nw_proto);
+                cls_rule_set_nw_proto(&fm->cr, p->nw_proto);
             }
         } else {
             const struct field *f;
@@ -562,33 +760,49 @@ parse_ofp_str(struct parsed_flow *pf, struct ofpbuf *actions, char *string)
                 ovs_fatal(0, "field %s missing value", name);
             }
 
-            if (!strcmp(name, "table")) {
-                pf->table_idx = atoi(value);
+            if (table_idx && !strcmp(name, "table")) {
+                *table_idx = atoi(value);
             } else if (!strcmp(name, "out_port")) {
-                pf->out_port = atoi(value);
+                fm->out_port = atoi(value);
             } else if (!strcmp(name, "priority")) {
-                pf->rule.priority = atoi(value);
+                fm->cr.priority = atoi(value);
             } else if (!strcmp(name, "idle_timeout")) {
-                pf->idle_timeout = atoi(value);
+                fm->idle_timeout = atoi(value);
             } else if (!strcmp(name, "hard_timeout")) {
-                pf->hard_timeout = atoi(value);
+                fm->hard_timeout = atoi(value);
             } else if (!strcmp(name, "cookie")) {
-                pf->cookie = str_to_u64(value);
+                fm->cookie = htonll(str_to_u64(value));
             } else if (parse_field_name(name, &f)) {
                 if (!strcmp(value, "*") || !strcmp(value, "ANY")) {
                     if (f->wildcard) {
-                        pf->rule.wc.wildcards |= f->wildcard;
-                        cls_rule_zero_wildcarded_fields(&pf->rule);
+                        fm->cr.wc.wildcards |= f->wildcard;
+                        cls_rule_zero_wildcarded_fields(&fm->cr);
                     } else if (f->index == F_NW_SRC) {
-                        cls_rule_set_nw_src_masked(&pf->rule, 0, 0);
+                        cls_rule_set_nw_src_masked(&fm->cr, 0, 0);
                     } else if (f->index == F_NW_DST) {
-                        cls_rule_set_nw_dst_masked(&pf->rule, 0, 0);
+                        cls_rule_set_nw_dst_masked(&fm->cr, 0, 0);
+                    } else if (f->index == F_IPV6_SRC) {
+                        cls_rule_set_ipv6_src_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
+                    } else if (f->index == F_IPV6_DST) {
+                        cls_rule_set_ipv6_dst_masked(&fm->cr,
+                                &in6addr_any, &in6addr_any);
+                    } else if (f->index == F_DL_VLAN) {
+                        cls_rule_set_any_vid(&fm->cr);
+                    } else if (f->index == F_DL_VLAN_PCP) {
+                        cls_rule_set_any_pcp(&fm->cr);
                     } else {
                         NOT_REACHED();
                     }
                 } else {
-                    parse_field_value(&pf->rule, f->index, value);
+                    parse_field_value(&fm->cr, f->index, value);
                 }
+            } else if (!strncmp(name, "reg", 3) && isdigit(name[3])) {
+                unsigned int reg_idx = atoi(name + 3);
+                if (reg_idx >= FLOW_N_REGS) {
+                    ovs_fatal(0, "only %d registers supported", FLOW_N_REGS);
+                }
+                parse_reg_value(&fm->cr, reg_idx, value);
             } else {
                 ovs_fatal(0, "unknown keyword %s", name);
             }
@@ -596,43 +810,49 @@ parse_ofp_str(struct parsed_flow *pf, struct ofpbuf *actions, char *string)
     }
 }
 
-/* Parses 'string' as an OFPT_FLOW_MOD with command 'command' (one of OFPFC_*)
- * and returns an ofpbuf that contains it. */
-struct ofpbuf *
-parse_ofp_flow_mod_str(char *string, uint16_t command)
+/* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command'
+ * (one of OFPFC_*) and appends the parsed OpenFlow message to 'packets'.
+ * '*cur_format' should initially contain the flow format currently configured
+ * on the connection; this function will add a message to change the flow
+ * format and update '*cur_format', if this is necessary to add the parsed
+ * flow. */
+void
+parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
+                       char *string, uint16_t command)
 {
-    struct parsed_flow pf;
-    struct ofpbuf *buffer;
-    struct ofp_flow_mod *ofm;
-
-    /* parse_ofp_str() will expand and reallocate the data in 'buffer', so we
-     * can't keep pointers to across the parse_ofp_str() call. */
-    make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
-    parse_ofp_str(&pf, buffer, string);
-
-    ofm = buffer->data;
-    flow_to_match(&pf.rule.flow, pf.rule.wc.wildcards, NXFF_OPENFLOW10,
-                  &ofm->match);
-    ofm->command = htons(command);
-    ofm->cookie = htonll(pf.cookie);
-    ofm->idle_timeout = htons(pf.idle_timeout);
-    ofm->hard_timeout = htons(pf.hard_timeout);
-    ofm->buffer_id = htonl(UINT32_MAX);
-    ofm->out_port = htons(pf.out_port);
-    ofm->priority = htons(pf.rule.priority);
-    update_openflow_length(buffer);
-
-    return buffer;
+    bool is_del = command == OFPFC_DELETE || command == OFPFC_DELETE_STRICT;
+    enum nx_flow_format min_format, next_format;
+    struct ofpbuf actions;
+    struct ofpbuf *ofm;
+    struct flow_mod fm;
+
+    ofpbuf_init(&actions, 64);
+    parse_ofp_str(&fm, NULL, is_del ? NULL : &actions, string);
+    fm.command = command;
+
+    min_format = ofputil_min_flow_format(&fm.cr, true, fm.cookie);
+    next_format = MAX(*cur_format, min_format);
+    if (next_format != *cur_format) {
+        struct ofpbuf *sff = ofputil_make_set_flow_format(next_format);
+        list_push_back(packets, &sff->list_node);
+        *cur_format = next_format;
+    }
+
+    ofm = ofputil_encode_flow_mod(&fm, *cur_format);
+    list_push_back(packets, &ofm->list_node);
+
+    ofpbuf_uninit(&actions);
 }
 
-/* Parses an OFPT_FLOW_MOD with subtype OFPFC_ADD from 'stream' and returns an
- * ofpbuf that contains it.  Returns a null pointer if end-of-file is reached
- * before reading a flow. */
-struct ofpbuf *
-parse_ofp_add_flow_file(FILE *stream)
+/* Similar to parse_ofp_flow_mod_str(), except that the string is read from
+ * 'stream' and the command is always OFPFC_ADD.  Returns false if end-of-file
+ * is reached before reading a flow, otherwise true. */
+bool
+parse_ofp_add_flow_file(struct list *packets, enum nx_flow_format *cur,
+                        FILE *stream)
 {
-    struct ofpbuf *b = NULL;
     struct ds s = DS_EMPTY_INITIALIZER;
+    bool ok = false;
 
     while (!ds_get_line(&s, stream)) {
         char *line = ds_cstr(&s);
@@ -649,10 +869,26 @@ parse_ofp_add_flow_file(FILE *stream)
             continue;
         }
 
-        b = parse_ofp_flow_mod_str(line, OFPFC_ADD);
+        parse_ofp_flow_mod_str(packets, cur, line, OFPFC_ADD);
+        ok = true;
         break;
     }
     ds_destroy(&s);
 
-    return b;
+    return ok;
 }
+
+void
+parse_ofp_flow_stats_request_str(struct flow_stats_request *fsr,
+                                 bool aggregate, char *string)
+{
+    struct flow_mod fm;
+    uint8_t table_id;
+
+    parse_ofp_str(&fm, &table_id, NULL, string);
+    fsr->aggregate = aggregate;
+    fsr->match = fm.cr;
+    fsr->out_port = fm.out_port;
+    fsr->table_id = table_id;
+}
+