]> git.proxmox.com Git - mirror_ovs.git/blobdiff - lib/ofp-parse.c
dpif-netdev: Reorder elements in dp_netdev_port structure.
[mirror_ovs.git] / lib / ofp-parse.c
index c346f102b6603915416fa3b7f10a906be256d00d..553991c77d8148c6c859505aeab2efae2fc3f8ff 100644 (file)
 
 #include <config.h>
 
-#include "ofp-parse.h"
-
 #include <ctype.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <netinet/in.h>
 
 #include "byte-order.h"
-#include "dynamic-string.h"
+#include "dp-packet.h"
 #include "learn.h"
-#include "meta-flow.h"
 #include "multipath.h"
 #include "netdev.h"
 #include "nx-match.h"
-#include "ofp-actions.h"
-#include "ofp-util.h"
-#include "ofpbuf.h"
 #include "openflow/openflow.h"
+#include "openvswitch/dynamic-string.h"
+#include "openvswitch/meta-flow.h"
+#include "openvswitch/ofp-actions.h"
+#include "openvswitch/ofp-parse.h"
+#include "openvswitch/ofp-util.h"
+#include "openvswitch/ofpbuf.h"
+#include "openvswitch/vconn.h"
 #include "ovs-thread.h"
 #include "packets.h"
 #include "simap.h"
 #include "socket-util.h"
-#include "openvswitch/vconn.h"
+#include "util.h"
 
 /* Parses 'str' as an 8-bit unsigned integer into '*valuep'.
  *
@@ -340,7 +341,6 @@ parse_ofp_str__(struct ofputil_flow_mod *fm, int command, char *string,
         .buffer_id = UINT32_MAX,
         .out_port = OFPP_ANY,
         .out_group = OFPG_ANY,
-        .delete_reason = OFPRR_DELETE,
     };
     /* For modify, by default, don't update the cookie. */
     if (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT) {
@@ -551,6 +551,108 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
     return error;
 }
 
+/* Parse a string representation of a OFPT_PACKET_OUT to '*po'.  If successful,
+ * both 'po->ofpacts' and 'po->packet' must be free()d by the caller. */
+static char * OVS_WARN_UNUSED_RESULT
+parse_ofp_packet_out_str__(struct ofputil_packet_out *po, char *string,
+                           enum ofputil_protocol *usable_protocols)
+{
+    enum ofputil_protocol action_usable_protocols;
+    uint64_t stub[256 / 8];
+    struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
+    struct dp_packet *packet = NULL;
+    char *act_str = NULL;
+    char *name, *value;
+    char *error = NULL;
+
+    *usable_protocols = OFPUTIL_P_ANY;
+
+    *po = (struct ofputil_packet_out) {
+        .buffer_id = UINT32_MAX,
+        .in_port = OFPP_CONTROLLER,
+    };
+
+    act_str = extract_actions(string);
+
+    while (ofputil_parse_key_value(&string, &name, &value)) {
+        if (!*value) {
+            error = xasprintf("field %s missing value", name);
+            goto out;
+        }
+
+        if (!strcmp(name, "in_port")) {
+            if (!ofputil_port_from_string(value, &po->in_port)) {
+                error = xasprintf("%s is not a valid OpenFlow port", value);
+                goto out;
+            }
+            if (ofp_to_u16(po->in_port) > ofp_to_u16(OFPP_MAX)
+                && po->in_port != OFPP_LOCAL
+                && po->in_port != OFPP_NONE
+                && po->in_port != OFPP_CONTROLLER) {
+                error = xasprintf(
+                              "%s is not a valid OpenFlow port for PACKET_OUT",
+                              value);
+                goto out;
+            }
+        } else if (!strcmp(name, "packet")) {
+            const char *error_msg = eth_from_hex(value, &packet);
+            if (error_msg) {
+                error = xasprintf("%s: %s", name, error_msg);
+                goto out;
+            }
+        } else {
+            error = xasprintf("unknown keyword %s", name);
+            goto out;
+        }
+    }
+
+    if (!packet || !dp_packet_size(packet)) {
+        error = xstrdup("must specify packet");
+        goto out;
+    }
+
+    if (act_str) {
+        error = ofpacts_parse_actions(act_str, &ofpacts,
+                                      &action_usable_protocols);
+        *usable_protocols &= action_usable_protocols;
+        if (error) {
+            goto out;
+        }
+    }
+    po->ofpacts_len = ofpacts.size;
+    po->ofpacts = ofpbuf_steal_data(&ofpacts);
+
+    po->packet_len = dp_packet_size(packet);
+    po->packet = dp_packet_steal_data(packet);
+out:
+    ofpbuf_uninit(&ofpacts);
+    dp_packet_delete(packet);
+    return error;
+}
+
+/* Convert 'str_' (as described in the Packet-Out Syntax section of the
+ * ovs-ofctl man page) into 'po' for sending a OFPT_PACKET_OUT message to a
+ * switch.  Returns the set of usable protocols in '*usable_protocols'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error.  The caller is responsible for freeing the returned string. */
+char * OVS_WARN_UNUSED_RESULT
+parse_ofp_packet_out_str(struct ofputil_packet_out *po, const char *str_,
+                         enum ofputil_protocol *usable_protocols)
+{
+    char *string = xstrdup(str_);
+    char *error;
+
+    error = parse_ofp_packet_out_str__(po, string, usable_protocols);
+    if (error) {
+        po->ofpacts = NULL;
+        po->ofpacts_len = 0;
+    }
+
+    free(string);
+    return error;
+}
+
 static char * OVS_WARN_UNUSED_RESULT
 parse_ofp_meter_mod_str__(struct ofputil_meter_mod *mm, char *string,
                           struct ofpbuf *bands, int command,
@@ -893,34 +995,46 @@ parse_ofp_table_vacancy(struct ofputil_table_mod *tm, const char *setting)
 {
     char *save_ptr = NULL;
     char *vac_up, *vac_down;
-    char *value = strdup(setting);
+    char *value = xstrdup(setting);
+    char *ret_msg;
     int vacancy_up, vacancy_down;
 
     strtok_r(value, ":", &save_ptr);
     vac_down = strtok_r(NULL, ",", &save_ptr);
     if (!vac_down) {
-        return xasprintf("Vacancy down value missing");
+        ret_msg = xasprintf("Vacancy down value missing");
+        goto exit;
     }
     if (!str_to_int(vac_down, 0, &vacancy_down) ||
         vacancy_down < 0 || vacancy_down > 100) {
-        return xasprintf("Invalid vacancy down value \"%s\"", vac_down);
+        ret_msg = xasprintf("Invalid vacancy down value \"%s\"", vac_down);
+        goto exit;
     }
     vac_up = strtok_r(NULL, ",", &save_ptr);
     if (!vac_up) {
-        return xasprintf("Vacancy up value missing");
+        ret_msg = xasprintf("Vacancy up value missing");
+        goto exit;
     }
     if (!str_to_int(vac_up, 0, &vacancy_up) ||
         vacancy_up < 0 || vacancy_up > 100) {
-        return xasprintf("Invalid vacancy up value \"%s\"", vac_up);
+        ret_msg = xasprintf("Invalid vacancy up value \"%s\"", vac_up);
+        goto exit;
     }
     if (vacancy_down > vacancy_up) {
-        return xasprintf("Invalid vacancy range, vacancy up should be greater"
-                         " than vacancy down ""(%s)",
-                         ofperr_to_string(OFPERR_OFPBPC_BAD_VALUE));
+        ret_msg = xasprintf("Invalid vacancy range, vacancy up should be "
+                            "greater than vacancy down (%s)",
+                            ofperr_to_string(OFPERR_OFPBPC_BAD_VALUE));
+        goto exit;
     }
+
+    free(value);
     tm->table_vacancy.vacancy_down = vacancy_down;
     tm->table_vacancy.vacancy_up = vacancy_up;
     return NULL;
+
+exit:
+    free(value);
+    return ret_msg;
 }
 
 /* Convert 'table_id' and 'setting' (as described for the "mod-table" command
@@ -1039,6 +1153,7 @@ parse_ofp_flow_mod_file(const char *file_name, int command,
         error = parse_ofp_flow_mod_str(&(*fms)[*n_fms], ds_cstr(&s), command,
                                        &usable);
         if (error) {
+            char *err_msg;
             size_t i;
 
             for (i = 0; i < *n_fms; i++) {
@@ -1053,7 +1168,9 @@ parse_ofp_flow_mod_file(const char *file_name, int command,
                 fclose(stream);
             }
 
-            return xasprintf("%s:%d: %s", file_name, line_number, error);
+            err_msg = xasprintf("%s:%d: %s", file_name, line_number, error);
+            free(error);
+            return err_msg;
         }
         *usable_protocols &= usable; /* Each line can narrow the set. */
         *n_fms += 1;
@@ -1100,14 +1217,15 @@ parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
 /* Parses a specification of a flow from 's' into 'flow'.  's' must take the
  * form FIELD=VALUE[,FIELD=VALUE]... where each FIELD is the name of a
  * mf_field.  Fields must be specified in a natural order for satisfying
- * prerequisites. If 'mask' is specified, fills the mask field for each of the
+ * prerequisites. If 'wc' is specified, masks the field in 'wc' for each of the
  * field specified in flow. If the map, 'names_portno' is specfied, converts
  * the in_port name into port no while setting the 'flow'.
  *
  * Returns NULL on success, otherwise a malloc()'d string that explains the
  * problem. */
 char *
-parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
+parse_ofp_exact_flow(struct flow *flow, struct flow_wildcards *wc,
+                     const struct tun_table *tun_table, const char *s,
                      const struct simap *portno_names)
 {
     char *pos, *key, *value_s;
@@ -1115,9 +1233,10 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
     char *copy;
 
     memset(flow, 0, sizeof *flow);
-    if (mask) {
-        memset(mask, 0, sizeof *mask);
+    if (wc) {
+        memset(wc, 0, sizeof *wc);
     }
+    flow->tunnel.metadata.tab = tun_table;
 
     pos = copy = xstrdup(s);
     while (ofputil_parse_key_value(&pos, &key, &value_s)) {
@@ -1128,8 +1247,8 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
                 goto exit;
             }
             flow->dl_type = htons(p->dl_type);
-            if (mask) {
-                mask->dl_type = OVS_BE16_MAX;
+            if (wc) {
+                wc->masks.dl_type = OVS_BE16_MAX;
             }
 
             if (p->nw_proto) {
@@ -1139,8 +1258,8 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
                     goto exit;
                 }
                 flow->nw_proto = p->nw_proto;
-                if (mask) {
-                    mask->nw_proto = UINT8_MAX;
+                if (wc) {
+                    wc->masks.nw_proto = UINT8_MAX;
                 }
             }
         } else {
@@ -1154,7 +1273,7 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
                 goto exit;
             }
 
-            if (!mf_are_prereqs_ok(mf, flow)) {
+            if (!mf_are_prereqs_ok(mf, flow, NULL)) {
                 error = xasprintf("%s: prerequisites not met for setting %s",
                                   s, key);
                 goto exit;
@@ -1170,8 +1289,9 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
                 && simap_contains(portno_names, value_s)) {
                 flow->in_port.ofp_port = u16_to_ofp(
                     simap_get(portno_names, value_s));
-                if (mask) {
-                    mask->in_port.ofp_port = u16_to_ofp(ntohs(OVS_BE16_MAX));
+                if (wc) {
+                    wc->masks.in_port.ofp_port
+                        = u16_to_ofp(ntohs(OVS_BE16_MAX));
                 }
             } else {
                 field_error = mf_parse_value(mf, value_s, &value);
@@ -1183,8 +1303,8 @@ parse_ofp_exact_flow(struct flow *flow, struct flow *mask, const char *s,
                 }
 
                 mf_set_flow_value(mf, &value, flow);
-                if (mask) {
-                    mf_mask_field(mf, mask);
+                if (wc) {
+                    mf_mask_field(mf, wc);
                 }
             }
         }
@@ -1199,8 +1319,8 @@ exit:
 
     if (error) {
         memset(flow, 0, sizeof *flow);
-        if (mask) {
-            memset(mask, 0, sizeof *mask);
+        if (wc) {
+            memset(wc, 0, sizeof *wc);
         }
     }
     return error;
@@ -1336,7 +1456,7 @@ parse_select_group_field(char *s, struct field_array *fa,
 }
 
 static char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
+parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, int command,
                           char *string,
                           enum ofputil_protocol *usable_protocols)
 {
@@ -1353,6 +1473,31 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
 
     *usable_protocols = OFPUTIL_P_OF11_UP;
 
+    if (command == -2) {
+        size_t len;
+
+        string += strspn(string, " \t\r\n");   /* Skip white space. */
+        len = strcspn(string, ", \t\r\n"); /* Get length of the first token. */
+
+        if (!strncmp(string, "add", len)) {
+            command = OFPGC11_ADD;
+        } else if (!strncmp(string, "delete", len)) {
+            command = OFPGC11_DELETE;
+        } else if (!strncmp(string, "modify", len)) {
+            command = OFPGC11_MODIFY;
+        } else if (!strncmp(string, "add_or_mod", len)) {
+            command = OFPGC11_ADD_OR_MOD;
+        } else if (!strncmp(string, "insert_bucket", len)) {
+            command = OFPGC15_INSERT_BUCKET;
+        } else if (!strncmp(string, "remove_bucket", len)) {
+            command = OFPGC15_REMOVE_BUCKET;
+        } else {
+            len = 0;
+            command = OFPGC11_ADD;
+        }
+        string += len;
+    }
+
     switch (command) {
     case OFPGC11_ADD:
         fields = F_GROUP_TYPE | F_BUCKETS;
@@ -1366,6 +1511,10 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
         fields = F_GROUP_TYPE | F_BUCKETS;
         break;
 
+    case OFPGC11_ADD_OR_MOD:
+        fields = F_GROUP_TYPE | F_BUCKETS;
+        break;
+
     case OFPGC15_INSERT_BUCKET:
         fields = F_BUCKETS | F_COMMAND_BUCKET_ID;
         *usable_protocols &= OFPUTIL_P_OF15_UP;
@@ -1384,7 +1533,7 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
     gm->command = command;
     gm->group_id = OFPG_ANY;
     gm->command_bucket_id = OFPG15_BUCKET_ALL;
-    list_init(&gm->buckets);
+    ovs_list_init(&gm->buckets);
     if (command == OFPGC11_DELETE && string[0] == '\0') {
         gm->group_id = OFPG_ALL;
         return NULL;
@@ -1522,6 +1671,18 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
         goto out;
     }
 
+    /* Exclude fields for non "hash" selection method. */
+    if (strcmp(gm->props.selection_method, "hash") &&
+        gm->props.fields.values_size) {
+        error = xstrdup("fields may only be specified with \"selection_method=hash\"");
+        goto out;
+    }
+    /* Exclude selection_method_param if no selection_method is given. */
+    if (gm->props.selection_method[0] == 0
+        && gm->props.selection_method_param != 0) {
+        error = xstrdup("selection_method_param is only allowed with \"selection_method\"");
+        goto out;
+    }
     if (fields & F_COMMAND_BUCKET_ID) {
         if (!(fields & F_COMMAND_BUCKET_ID_ALL || had_command_bucket_id)) {
             error = xstrdup("must specify a command bucket id");
@@ -1554,7 +1715,7 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
             free(bucket);
             goto out;
         }
-        list_push_back(&gm->buckets, &bucket->list_node);
+        ovs_list_push_back(&gm->buckets, &bucket->list_node);
 
         if (gm->type != OFPGT11_SELECT && bucket->weight) {
             error = xstrdup("Only select groups can have bucket weights.");
@@ -1563,19 +1724,23 @@ parse_ofp_group_mod_str__(struct ofputil_group_mod *gm, uint16_t command,
 
         bkt_str = next_bkt_str;
     }
-    if (gm->type == OFPGT11_INDIRECT && !list_is_short(&gm->buckets)) {
+    if (gm->type == OFPGT11_INDIRECT && !ovs_list_is_short(&gm->buckets)) {
         error = xstrdup("Indirect groups can have at most one bucket.");
         goto out;
     }
 
     return NULL;
  out:
-    ofputil_bucket_list_destroy(&gm->buckets);
+    ofputil_uninit_group_mod(gm);
     return error;
 }
 
+/* If 'command' is given as -2, each line may start with a command name ("add",
+ * "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket").  A
+ * missing command name is treated as "add".
+ */
 char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command,
+parse_ofp_group_mod_str(struct ofputil_group_mod *gm, int command,
                         const char *str_,
                         enum ofputil_protocol *usable_protocols)
 {
@@ -1583,15 +1748,15 @@ parse_ofp_group_mod_str(struct ofputil_group_mod *gm, uint16_t command,
     char *error = parse_ofp_group_mod_str__(gm, command, string,
                                             usable_protocols);
     free(string);
-
-    if (error) {
-        ofputil_bucket_list_destroy(&gm->buckets);
-    }
     return error;
 }
 
+/* If 'command' is given as -2, each line may start with a command name ("add",
+ * "modify", "add_or_mod", "delete", "insert_bucket", or "remove_bucket").  A
+ * missing command name is treated as "add".
+ */
 char * OVS_WARN_UNUSED_RESULT
-parse_ofp_group_mod_file(const char *file_name, uint16_t command,
+parse_ofp_group_mod_file(const char *file_name, int command,
                          struct ofputil_group_mod **gms, size_t *n_gms,
                          enum ofputil_protocol *usable_protocols)
 {
@@ -1623,7 +1788,7 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command,
 
             new_gms = x2nrealloc(*gms, &allocated_gms, sizeof **gms);
             for (i = 0; i < *n_gms; i++) {
-                list_moved(&new_gms[i].buckets, &(*gms)[i].buckets);
+                ovs_list_moved(&new_gms[i].buckets, &(*gms)[i].buckets);
             }
             *gms = new_gms;
         }
@@ -1633,7 +1798,7 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command,
             size_t i;
 
             for (i = 0; i < *n_gms; i++) {
-                ofputil_bucket_list_destroy(&(*gms)[i].buckets);
+                ofputil_uninit_group_mod(&(*gms)[i]);
             }
             free(*gms);
             *gms = NULL;
@@ -1657,6 +1822,108 @@ parse_ofp_group_mod_file(const char *file_name, uint16_t command,
     return NULL;
 }
 
+/* Opens file 'file_name' and reads each line as a flow_mod or a group_mod,
+ * depending on the first keyword on each line.  Stores each flow and group
+ * mods in '*bms', an array allocated on the caller's behalf, and the number of
+ * messages in '*n_bms'.
+ *
+ * Returns NULL if successful, otherwise a malloc()'d string describing the
+ * error.  The caller is responsible for freeing the returned string. */
+char * OVS_WARN_UNUSED_RESULT
+parse_ofp_bundle_file(const char *file_name,
+                      struct ofputil_bundle_msg **bms, size_t *n_bms,
+                      enum ofputil_protocol *usable_protocols)
+{
+    size_t allocated_bms;
+    char *error = NULL;
+    int line_number;
+    FILE *stream;
+    struct ds ds;
+
+    *usable_protocols = OFPUTIL_P_ANY;
+
+    *bms = NULL;
+    *n_bms = 0;
+
+    stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
+    if (stream == NULL) {
+        return xasprintf("%s: open failed (%s)",
+                         file_name, ovs_strerror(errno));
+    }
+
+    allocated_bms = *n_bms;
+    ds_init(&ds);
+    line_number = 0;
+    while (!ds_get_preprocessed_line(&ds, stream, &line_number)) {
+        enum ofputil_protocol usable;
+        char *s = ds_cstr(&ds);
+        size_t len;
+
+        if (*n_bms >= allocated_bms) {
+            struct ofputil_bundle_msg *new_bms;
+
+            new_bms = x2nrealloc(*bms, &allocated_bms, sizeof **bms);
+            for (size_t i = 0; i < *n_bms; i++) {
+                if (new_bms[i].type == OFPTYPE_GROUP_MOD) {
+                    ovs_list_moved(&new_bms[i].gm.buckets,
+                                   &(*bms)[i].gm.buckets);
+                }
+            }
+            *bms = new_bms;
+        }
+
+        s += strspn(s, " \t\r\n");   /* Skip white space. */
+        len = strcspn(s, ", \t\r\n"); /* Get length of the first token. */
+
+        if (!strncmp(s, "flow", len)) {
+            s += len;
+            error = parse_ofp_flow_mod_str(&(*bms)[*n_bms].fm, s, -2, &usable);
+            if (error) {
+                break;
+            }
+            (*bms)[*n_bms].type = OFPTYPE_FLOW_MOD;
+        } else if (!strncmp(s, "group", len)) {
+            s += len;
+            error = parse_ofp_group_mod_str(&(*bms)[*n_bms].gm, -2, s,
+                                            &usable);
+            if (error) {
+                break;
+            }
+            (*bms)[*n_bms].type = OFPTYPE_GROUP_MOD;
+        } else if (!strncmp(s, "packet-out", len)) {
+            s += len;
+            error = parse_ofp_packet_out_str(&(*bms)[*n_bms].po, s, &usable);
+            if (error) {
+                break;
+            }
+            (*bms)[*n_bms].type = OFPTYPE_PACKET_OUT;
+        } else {
+            error = xasprintf("Unsupported bundle message type: %.*s",
+                              (int)len, s);
+            break;
+        }
+
+        *usable_protocols &= usable; /* Each line can narrow the set. */
+        *n_bms += 1;
+    }
+
+    ds_destroy(&ds);
+    if (stream != stdin) {
+        fclose(stream);
+    }
+
+    if (error) {
+        char *err_msg = xasprintf("%s:%d: %s", file_name, line_number, error);
+        free(error);
+
+        ofputil_free_bundle_msgs(*bms, *n_bms);
+        *bms = NULL;
+        *n_bms = 0;
+        return err_msg;
+    }
+    return NULL;
+}
+
 char * OVS_WARN_UNUSED_RESULT
 parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *ttm,
                                uint16_t command, const char *s,
@@ -1665,7 +1932,7 @@ parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *ttm,
     *usable_protocols = OFPUTIL_P_NXM_OXM_ANY;
 
     ttm->command = command;
-    list_init(&ttm->mappings);
+    ovs_list_init(&ttm->mappings);
 
     while (*s) {
         struct ofputil_tlv_map *map = xmalloc(sizeof *map);
@@ -1675,7 +1942,7 @@ parse_ofp_tlv_table_mod_str(struct ofputil_tlv_table_mod *ttm,
             s++;
         }
 
-        list_push_back(&ttm->mappings, &map->list_node);
+        ovs_list_push_back(&ttm->mappings, &map->list_node);
 
         if (!ovs_scan(s, "{class=%"SCNi16",type=%"SCNi8",len=%"SCNi8"}->tun_metadata%"SCNi16"%n",
                       &map->option_class, &map->option_type, &map->option_len,