+static bool
+check_attr_len(struct ds *ds, const struct nlattr *a, const struct nlattr *ma,
+ const struct attr_len_tbl tbl[], int max_len, bool need_key)
+{
+ int expected_len;
+
+ expected_len = odp_key_attr_len(tbl, max_len, nl_attr_type(a));
+ if (expected_len != ATTR_LEN_VARIABLE &&
+ expected_len != ATTR_LEN_NESTED) {
+
+ bool bad_key_len = nl_attr_get_size(a) != expected_len;
+ bool bad_mask_len = ma && nl_attr_get_size(ma) != expected_len;
+
+ if (bad_key_len || bad_mask_len) {
+ if (need_key) {
+ ds_put_format(ds, "key%u", nl_attr_type(a));
+ }
+ if (bad_key_len) {
+ ds_put_format(ds, "(bad key length %"PRIuSIZE", expected %d)(",
+ nl_attr_get_size(a), expected_len);
+ }
+ format_generic_odp_key(a, ds);
+ if (ma) {
+ ds_put_char(ds, '/');
+ if (bad_mask_len) {
+ ds_put_format(ds, "(bad mask length %"PRIuSIZE", expected %d)(",
+ nl_attr_get_size(ma), expected_len);
+ }
+ format_generic_odp_key(ma, ds);
+ }
+ ds_put_char(ds, ')');
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void
+format_unknown_key(struct ds *ds, const struct nlattr *a,
+ const struct nlattr *ma)
+{
+ ds_put_format(ds, "key%u(", nl_attr_type(a));
+ format_generic_odp_key(a, ds);
+ if (ma && !odp_mask_attr_is_exact(ma)) {
+ ds_put_char(ds, '/');
+ format_generic_odp_key(ma, ds);
+ }
+ ds_put_cstr(ds, "),");
+}
+
+static void
+format_odp_tun_vxlan_opt(const struct nlattr *attr,
+ const struct nlattr *mask_attr, struct ds *ds,
+ bool verbose)
+{
+ unsigned int left;
+ const struct nlattr *a;
+ struct ofpbuf ofp;
+
+ ofpbuf_init(&ofp, 100);
+ NL_NESTED_FOR_EACH(a, left, attr) {
+ uint16_t type = nl_attr_type(a);
+ const struct nlattr *ma = NULL;
+
+ if (mask_attr) {
+ ma = nl_attr_find__(nl_attr_get(mask_attr),
+ nl_attr_get_size(mask_attr), type);
+ if (!ma) {
+ ma = generate_all_wildcard_mask(ovs_vxlan_ext_attr_lens,
+ OVS_VXLAN_EXT_MAX,
+ &ofp, a);
+ }
+ }
+
+ if (!check_attr_len(ds, a, ma, ovs_vxlan_ext_attr_lens,
+ OVS_VXLAN_EXT_MAX, true)) {
+ continue;
+ }
+
+ switch (type) {
+ case OVS_VXLAN_EXT_GBP: {
+ uint32_t key = nl_attr_get_u32(a);
+ ovs_be16 id, id_mask;
+ uint8_t flags, flags_mask;
+
+ id = htons(key & 0xFFFF);
+ flags = (key >> 16) & 0xFF;
+ if (ma) {
+ uint32_t mask = nl_attr_get_u32(ma);
+ id_mask = htons(mask & 0xFFFF);
+ flags_mask = (mask >> 16) & 0xFF;
+ }
+
+ ds_put_cstr(ds, "gbp(");
+ format_be16(ds, "id", id, ma ? &id_mask : NULL, verbose);
+ format_u8x(ds, "flags", flags, ma ? &flags_mask : NULL, verbose);
+ ds_chomp(ds, ',');
+ ds_put_cstr(ds, "),");
+ break;
+ }
+
+ default:
+ format_unknown_key(ds, a, ma);
+ }
+ ofpbuf_clear(&ofp);
+ }
+
+ ds_chomp(ds, ',');
+ ofpbuf_uninit(&ofp);
+}
+
+#define MASK(PTR, FIELD) PTR ? &PTR->FIELD : NULL
+
+static void
+format_geneve_opts(const struct geneve_opt *opt,
+ const struct geneve_opt *mask, int opts_len,
+ struct ds *ds, bool verbose)
+{
+ while (opts_len > 0) {
+ unsigned int len;
+ uint8_t data_len, data_len_mask;
+
+ if (opts_len < sizeof *opt) {
+ ds_put_format(ds, "opt len %u less than minimum %"PRIuSIZE,
+ opts_len, sizeof *opt);
+ return;
+ }
+
+ data_len = opt->length * 4;
+ if (mask) {
+ if (mask->length == 0x1f) {
+ data_len_mask = UINT8_MAX;
+ } else {
+ data_len_mask = mask->length;
+ }
+ }
+ len = sizeof *opt + data_len;
+ if (len > opts_len) {
+ ds_put_format(ds, "opt len %u greater than remaining %u",
+ len, opts_len);
+ return;
+ }
+
+ ds_put_char(ds, '{');
+ format_be16x(ds, "class", opt->opt_class, MASK(mask, opt_class),
+ verbose);
+ format_u8x(ds, "type", opt->type, MASK(mask, type), verbose);
+ format_u8u(ds, "len", data_len, mask ? &data_len_mask : NULL, verbose);
+ if (data_len &&
+ (verbose || !mask || !is_all_zeros(mask + 1, data_len))) {
+ ds_put_hex(ds, opt + 1, data_len);
+ if (mask && !is_all_ones(mask + 1, data_len)) {
+ ds_put_char(ds, '/');
+ ds_put_hex(ds, mask + 1, data_len);
+ }
+ } else {
+ ds_chomp(ds, ',');
+ }
+ ds_put_char(ds, '}');
+
+ opt += len / sizeof(*opt);
+ if (mask) {
+ mask += len / sizeof(*opt);
+ }
+ opts_len -= len;
+ };
+}
+
+static void
+format_odp_tun_geneve(const struct nlattr *attr,
+ const struct nlattr *mask_attr, struct ds *ds,
+ bool verbose)
+{
+ int opts_len = nl_attr_get_size(attr);
+ const struct geneve_opt *opt = nl_attr_get(attr);
+ const struct geneve_opt *mask = mask_attr ?
+ nl_attr_get(mask_attr) : NULL;
+
+ if (mask && nl_attr_get_size(attr) != nl_attr_get_size(mask_attr)) {
+ ds_put_format(ds, "value len %"PRIuSIZE" different from mask len %"PRIuSIZE,
+ nl_attr_get_size(attr), nl_attr_get_size(mask_attr));
+ return;
+ }
+
+ format_geneve_opts(opt, mask, opts_len, ds, verbose);
+}
+
+static void
+format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
+ struct ds *ds, bool verbose)
+{
+ unsigned int left;
+ const struct nlattr *a;
+ uint16_t flags = 0;
+ uint16_t mask_flags = 0;
+ struct ofpbuf ofp;
+
+ ofpbuf_init(&ofp, 100);
+ NL_NESTED_FOR_EACH(a, left, attr) {
+ enum ovs_tunnel_key_attr type = nl_attr_type(a);
+ const struct nlattr *ma = NULL;
+
+ if (mask_attr) {
+ ma = nl_attr_find__(nl_attr_get(mask_attr),
+ nl_attr_get_size(mask_attr), type);
+ if (!ma) {
+ ma = generate_all_wildcard_mask(ovs_tun_key_attr_lens,
+ OVS_TUNNEL_KEY_ATTR_MAX,
+ &ofp, a);
+ }
+ }
+
+ if (!check_attr_len(ds, a, ma, ovs_tun_key_attr_lens,
+ OVS_TUNNEL_KEY_ATTR_MAX, true)) {
+ continue;
+ }
+
+ switch (type) {
+ case OVS_TUNNEL_KEY_ATTR_ID:
+ format_be64(ds, "tun_id", nl_attr_get_be64(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ flags |= FLOW_TNL_F_KEY;
+ if (ma) {
+ mask_flags |= FLOW_TNL_F_KEY;
+ }
+ break;
+ case OVS_TUNNEL_KEY_ATTR_IPV4_SRC:
+ format_ipv4(ds, "src", nl_attr_get_be32(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_IPV4_DST:
+ format_ipv4(ds, "dst", nl_attr_get_be32(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_IPV6_SRC: {
+ struct in6_addr ipv6_src;
+ ipv6_src = nl_attr_get_in6_addr(a);
+ format_in6_addr(ds, "ipv6_src", &ipv6_src,
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ }
+ case OVS_TUNNEL_KEY_ATTR_IPV6_DST: {
+ struct in6_addr ipv6_dst;
+ ipv6_dst = nl_attr_get_in6_addr(a);
+ format_in6_addr(ds, "ipv6_dst", &ipv6_dst,
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ }
+ case OVS_TUNNEL_KEY_ATTR_TOS:
+ format_u8x(ds, "tos", nl_attr_get_u8(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_TTL:
+ format_u8u(ds, "ttl", nl_attr_get_u8(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT:
+ flags |= FLOW_TNL_F_DONT_FRAGMENT;
+ break;
+ case OVS_TUNNEL_KEY_ATTR_CSUM:
+ flags |= FLOW_TNL_F_CSUM;
+ break;
+ case OVS_TUNNEL_KEY_ATTR_TP_SRC:
+ format_be16(ds, "tp_src", nl_attr_get_be16(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_TP_DST:
+ format_be16(ds, "tp_dst", nl_attr_get_be16(a),
+ ma ? nl_attr_get(ma) : NULL, verbose);
+ break;
+ case OVS_TUNNEL_KEY_ATTR_OAM:
+ flags |= FLOW_TNL_F_OAM;
+ break;
+ case OVS_TUNNEL_KEY_ATTR_VXLAN_OPTS:
+ ds_put_cstr(ds, "vxlan(");
+ format_odp_tun_vxlan_opt(a, ma, ds, verbose);
+ ds_put_cstr(ds, "),");
+ break;
+ case OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS:
+ ds_put_cstr(ds, "geneve(");
+ format_odp_tun_geneve(a, ma, ds, verbose);
+ ds_put_cstr(ds, "),");
+ break;
+ case __OVS_TUNNEL_KEY_ATTR_MAX:
+ default:
+ format_unknown_key(ds, a, ma);
+ }
+ ofpbuf_clear(&ofp);
+ }
+
+ /* Flags can have a valid mask even if the attribute is not set, so
+ * we need to collect these separately. */
+ if (mask_attr) {
+ NL_NESTED_FOR_EACH(a, left, mask_attr) {
+ switch (nl_attr_type(a)) {
+ case OVS_TUNNEL_KEY_ATTR_DONT_FRAGMENT:
+ mask_flags |= FLOW_TNL_F_DONT_FRAGMENT;
+ break;
+ case OVS_TUNNEL_KEY_ATTR_CSUM:
+ mask_flags |= FLOW_TNL_F_CSUM;
+ break;
+ case OVS_TUNNEL_KEY_ATTR_OAM:
+ mask_flags |= FLOW_TNL_F_OAM;
+ break;
+ }
+ }
+ }
+
+ format_tun_flags(ds, "flags", flags, mask_attr ? &mask_flags : NULL,
+ verbose);
+ ds_chomp(ds, ',');
+ ofpbuf_uninit(&ofp);
+}
+
+static const char *
+odp_ct_state_to_string(uint32_t flag)
+{
+ switch (flag) {
+ case OVS_CS_F_REPLY_DIR:
+ return "rpl";
+ case OVS_CS_F_TRACKED:
+ return "trk";
+ case OVS_CS_F_NEW:
+ return "new";
+ case OVS_CS_F_ESTABLISHED:
+ return "est";
+ case OVS_CS_F_RELATED:
+ return "rel";
+ case OVS_CS_F_INVALID:
+ return "inv";
+ case OVS_CS_F_SRC_NAT:
+ return "snat";
+ case OVS_CS_F_DST_NAT:
+ return "dnat";
+ default:
+ return NULL;
+ }
+}
+