diff --git a/lib/meta-flow.c b/lib/meta-flow.c
index e2e61f8..224ba53 100644
--- a/lib/meta-flow.c
+++ b/lib/meta-flow.c
@@ -796,11 +796,19 @@ mf_get_value(const struct mf_field *mf, const struct flow *flow,
 
 /* Makes 'match' match field 'mf' exactly, with the value matched taken from
  * 'value'.  The caller is responsible for ensuring that 'match' meets 'mf''s
- * prerequisites. */
+ * prerequisites.
+ *
+ * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
+ * with the request or NULL if there is no error. The caller is reponsible
+ * for freeing the string. */
 void
 mf_set_value(const struct mf_field *mf,
-             const union mf_value *value, struct match *match)
+             const union mf_value *value, struct match *match, char **err_str)
 {
+    if (err_str) {
+        *err_str = NULL;
+    }
+
     switch (mf->id) {
     case MFF_DP_HASH:
         match_set_dp_hash(match, ntohl(value->be32));
@@ -836,7 +844,7 @@ mf_set_value(const struct mf_field *mf,
         match_set_tun_ttl(match, value->u8);
         break;
     CASE_MFF_TUN_METADATA:
-        tun_metadata_set_match(mf, value, NULL, match);
+        tun_metadata_set_match(mf, value, NULL, match, err_str);
         break;
 
     case MFF_METADATA:
@@ -1364,10 +1372,18 @@ mf_is_set(const struct mf_field *mf, const struct flow *flow)
 /* Makes 'match' wildcard field 'mf'.
  *
  * The caller is responsible for ensuring that 'match' meets 'mf''s
- * prerequisites. */
+ * prerequisites.
+ *
+ * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
+ * with the request or NULL if there is no error. The caller is reponsible
+ * for freeing the string. */
 void
-mf_set_wild(const struct mf_field *mf, struct match *match)
+mf_set_wild(const struct mf_field *mf, struct match *match, char **err_str)
 {
+    if (err_str) {
+        *err_str = NULL;
+    }
+
     switch (mf->id) {
     case MFF_DP_HASH:
         match->flow.dp_hash = 0;
@@ -1406,7 +1422,7 @@ mf_set_wild(const struct mf_field *mf, struct match *match)
         match_set_tun_ttl_masked(match, 0, 0);
         break;
     CASE_MFF_TUN_METADATA:
-        tun_metadata_set_match(mf, NULL, NULL, match);
+        tun_metadata_set_match(mf, NULL, NULL, match, err_str);
         break;
 
     case MFF_METADATA:
@@ -1595,22 +1611,30 @@ mf_set_wild(const struct mf_field *mf, struct match *match)
  * call is equivalent to mf_set_wild(mf, match).
  *
  * 'mask' must be a valid mask for 'mf' (see mf_is_mask_valid()).  The caller
- * is responsible for ensuring that 'match' meets 'mf''s prerequisites. */
+ * is responsible for ensuring that 'match' meets 'mf''s prerequisites.
+ *
+ * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
+ * with the request or NULL if there is no error. The caller is reponsible
+ * for freeing the string.*/
 enum ofputil_protocol
 mf_set(const struct mf_field *mf,
        const union mf_value *value, const union mf_value *mask,
-       struct match *match)
+       struct match *match, char **err_str)
 {
     if (!mask || is_all_ones(mask, mf->n_bytes)) {
-        mf_set_value(mf, value, match);
+        mf_set_value(mf, value, match, err_str);
         return mf->usable_protocols_exact;
     } else if (is_all_zeros(mask, mf->n_bytes) && !mf_is_tun_metadata(mf)) {
         /* Tunnel metadata matches on the existence of the field itself, so
          * it still needs to be encoded even if the value is wildcarded. */
-        mf_set_wild(mf, match);
+        mf_set_wild(mf, match, err_str);
         return OFPUTIL_P_ANY;
     }
 
+    if (err_str) {
+        *err_str = NULL;
+    }
+
     switch (mf->id) {
     case MFF_RECIRC_ID:
     case MFF_CONJ_ID:
@@ -1665,7 +1689,7 @@ mf_set(const struct mf_field *mf,
         match_set_tun_tos_masked(match, value->u8, mask->u8);
         break;
     CASE_MFF_TUN_METADATA:
-        tun_metadata_set_match(mf, value, mask, match);
+        tun_metadata_set_match(mf, value, mask, match, err_str);
         break;
 
     case MFF_METADATA:
@@ -1731,7 +1755,7 @@ mf_set(const struct mf_field *mf,
 
     case MFF_IPV6_LABEL:
         if ((mask->be32 & htonl(IPV6_LABEL_MASK)) == htonl(IPV6_LABEL_MASK)) {
-            mf_set_value(mf, value, match);
+            mf_set_value(mf, value, match, err_str);
         } else {
             match_set_ipv6_label_masked(match, value->be32, mask->be32);
         }
@@ -2316,7 +2340,7 @@ mf_write_subfield(const struct mf_subfield *sf, const union mf_subvalue *x,
     mf_get(field, match, &value, &mask);
     bitwise_copy(x, sizeof *x, 0, &value, field->n_bytes, sf->ofs, sf->n_bits);
     bitwise_one (                 &mask,  field->n_bytes, sf->ofs, sf->n_bits);
-    mf_set(field, &value, &mask, match);
+    mf_set(field, &value, &mask, match, NULL);
 }
 
 /* 'v' and 'm' correspond to values of 'field'.  This function copies them into
@@ -2332,7 +2356,7 @@ mf_mask_subfield(const struct mf_field *field,
     mf_get(field, match, &value, &mask);
     bitwise_copy(v, sizeof *v, 0, &value, field->n_bytes, 0, field->n_bits);
     bitwise_copy(m, sizeof *m, 0, &mask,  field->n_bytes, 0, field->n_bits);
-    mf_set(field, &value, &mask, match);
+    mf_set(field, &value, &mask, match, NULL);
 }
 
 /* Initializes 'x' to the value of 'sf' within 'flow'.  'sf' must be valid for
diff --git a/lib/meta-flow.h b/lib/meta-flow.h
index cfc6263..02272ef 100644
--- a/lib/meta-flow.h
+++ b/lib/meta-flow.h
@@ -1844,7 +1844,7 @@ bool mf_is_value_valid(const struct mf_field *, const union mf_value *value);
 void mf_get_value(const struct mf_field *, const struct flow *,
                   union mf_value *value);
 void mf_set_value(const struct mf_field *, const union mf_value *value,
-                  struct match *);
+                  struct match *, char **err_str);
 void mf_set_flow_value(const struct mf_field *, const union mf_value *value,
                        struct flow *);
 void mf_set_flow_value_masked(const struct mf_field *,
@@ -1864,9 +1864,9 @@ void mf_get(const struct mf_field *, const struct match *,
 enum ofputil_protocol mf_set(const struct mf_field *,
                              const union mf_value *value,
                              const union mf_value *mask,
-                             struct match *);
+                             struct match *, char **err_str);
 
-void mf_set_wild(const struct mf_field *, struct match *);
+void mf_set_wild(const struct mf_field *, struct match *, char **err_str);
 
 /* Subfields. */
 void mf_write_subfield_flow(const struct mf_subfield *,
diff --git a/lib/nx-match.c b/lib/nx-match.c
index 84283b3..eef2c54 100644
--- a/lib/nx-match.c
+++ b/lib/nx-match.c
@@ -511,7 +511,15 @@ nx_pull_raw(const uint8_t *p, unsigned int match_len, bool strict,
         } else if (!mf_is_all_wild(field, &match->wc)) {
             error = OFPERR_OFPBMC_DUP_FIELD;
         } else {
-            mf_set(field, &value, &mask, match);
+            char *err_str;
+
+            mf_set(field, &value, &mask, match, &err_str);
+            if (err_str) {
+                VLOG_DBG_RL(&rl, "error parsing OXM at offset %"PRIdPTR" "
+                           "within match (%s)", pos - p, err_str);
+                free(err_str);
+                return OFPERR_OFPBMC_BAD_VALUE;
+            }
         }
 
         if (error) {
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 855d732..e8b831d 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -229,7 +229,7 @@ parse_field(const struct mf_field *mf, const char *s, struct match *match,
 
     error = mf_parse(mf, s, &value, &mask);
     if (!error) {
-        *usable_protocols &= mf_set(mf, &value, &mask, match);
+        *usable_protocols &= mf_set(mf, &value, &mask, match, &error);
     }
     return error;
 }
diff --git a/lib/tun-metadata.c b/lib/tun-metadata.c
index 0a4090d..3c66eba 100644
--- a/lib/tun-metadata.c
+++ b/lib/tun-metadata.c
@@ -280,7 +280,8 @@ tun_metadata_write(struct flow_tnl *tnl,
 
 static const struct tun_metadata_loc *
 metadata_loc_from_match(struct tun_table *map, struct match *match,
-                        unsigned int idx, unsigned int field_len, bool masked)
+                        const char *name, unsigned int idx,
+                        unsigned int field_len, bool masked, char **err_str)
 {
     ovs_assert(idx < TUN_METADATA_NUM_OPTS);
 
@@ -292,8 +293,22 @@ metadata_loc_from_match(struct tun_table *map, struct match *match,
         }
     }
 
-    if (match->tun_md.alloc_offset + field_len >= TUN_METADATA_TOT_OPT_SIZE ||
-        ULLONG_GET(match->wc.masks.tunnel.metadata.present.map, idx)) {
+    if (match->tun_md.alloc_offset + field_len > TUN_METADATA_TOT_OPT_SIZE) {
+        if (err_str) {
+            *err_str = xasprintf("field %s exceeds maximum size for tunnel "
+                                 "metadata (used %d, max %d)", name,
+                                 match->tun_md.alloc_offset + field_len,
+                                 TUN_METADATA_TOT_OPT_SIZE);
+        }
+
+        return NULL;
+    }
+
+    if (ULLONG_GET(match->wc.masks.tunnel.metadata.present.map, idx)) {
+        if (err_str) {
+            *err_str = xasprintf("field %s set multiple times", name);
+        }
+
         return NULL;
     }
 
@@ -322,10 +337,15 @@ metadata_loc_from_match(struct tun_table *map, struct match *match,
  * value.
  *
  * 'mask' may be NULL; if so, then 'mf' is made exact-match.
+ *
+ * If non-NULL, 'err_str' returns a malloc'ed string describing any errors
+ * with the request or NULL if there is no error. The caller is reponsible
+ * for freeing the string.
  */
 void
 tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
-                       const union mf_value *mask, struct match *match)
+                       const union mf_value *mask, struct match *match,
+                       char **err_str)
 {
     struct tun_table *map = ovsrcu_get(struct tun_table *, &metadata_tab);
     const struct tun_metadata_loc *loc;
@@ -338,7 +358,8 @@ tun_metadata_set_match(const struct mf_field *mf, const union mf_value *value,
     ovs_assert(!(match->flow.tunnel.flags & FLOW_TNL_F_UDPIF));
 
     field_len = mf_field_len(mf, value, mask, &is_masked);
-    loc = metadata_loc_from_match(map, match, idx, field_len, is_masked);
+    loc = metadata_loc_from_match(map, match, mf->name, idx, field_len,
+                                  is_masked, err_str);
     if (!loc) {
         return;
     }
@@ -424,8 +445,8 @@ tun_metadata_get_fmd(const struct flow_tnl *tnl, struct match *flow_metadata)
         const struct tun_metadata_loc *old_loc = &flow.metadata.tab->entries[i].loc;
         const struct tun_metadata_loc *new_loc;
 
-        new_loc = metadata_loc_from_match(NULL, flow_metadata, i,
-                                          old_loc->len, false);
+        new_loc = metadata_loc_from_match(NULL, flow_metadata, NULL, i,
+                                          old_loc->len, false, NULL);
 
         memcpy_from_metadata(opts.tun_metadata, &flow.metadata, old_loc);
         memcpy_to_metadata(&flow_metadata->flow.tunnel.metadata,
diff --git a/lib/tun-metadata.h b/lib/tun-metadata.h
index 85652ad..624c881 100644
--- a/lib/tun-metadata.h
+++ b/lib/tun-metadata.h
@@ -77,8 +77,8 @@ BUILD_ASSERT_DECL(sizeof(((struct tun_metadata *)0)->present.map) * 8 >=
  * linked list of these blocks. */
 struct tun_metadata_loc_chain {
     struct tun_metadata_loc_chain *next;
-    uint8_t offset;       /* In bytes, from start of 'opts', multiple of 4.  */
-    uint8_t len;          /* In bytes, multiple of 4. */
+    int offset;       /* In bytes, from start of 'opts', multiple of 4.  */
+    int len;          /* In bytes, multiple of 4. */
 };
 
 struct tun_metadata_loc {
@@ -102,7 +102,7 @@ struct tun_metadata_match_entry {
  * allocated memory because the address space is never fragmented. */
 struct tun_metadata_allocation {
     struct tun_metadata_match_entry entry[TUN_METADATA_NUM_OPTS];
-    uint8_t alloc_offset;       /* Byte offset into 'opts', multiple of 4.  */
+    int alloc_offset;           /* Byte offset into 'opts', multiple of 4.  */
     bool valid;                 /* Set to true after any allocation occurs. */
 };
 
@@ -117,7 +117,8 @@ void tun_metadata_write(struct flow_tnl *,
                         const struct mf_field *, const union mf_value *);
 void tun_metadata_set_match(const struct mf_field *,
                             const union mf_value *value,
-                            const union mf_value *mask, struct match *);
+                            const union mf_value *mask, struct match *,
+                            char **err_str);
 void tun_metadata_get_fmd(const struct flow_tnl *, struct match *flow_metadata);
 
 int tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
