+ 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";
+ default:
+ return NULL;
+ }
+}
+
+static void
+format_frag(struct ds *ds, const char *name, uint8_t key,
+ const uint8_t *mask, bool verbose)
+{
+ bool mask_empty = mask && !*mask;
+
+ /* ODP frag is an enumeration field; partial masks are not meaningful. */
+ if (verbose || !mask_empty) {
+ bool mask_full = !mask || *mask == UINT8_MAX;
+
+ if (!mask_full) { /* Partially masked. */
+ ds_put_format(ds, "error: partial mask not supported for frag (%#"
+ PRIx8"),", *mask);
+ } else {
+ ds_put_format(ds, "%s=%s,", name, ovs_frag_type_to_string(key));
+ }
+ }
+}
+
+static bool
+mask_empty(const struct nlattr *ma)
+{
+ const void *mask;
+ size_t n;
+
+ if (!ma) {
+ return true;
+ }
+ mask = nl_attr_get(ma);
+ n = nl_attr_get_size(ma);
+
+ return is_all_zeros(mask, n);
+}
+
+static void
+format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma,
+ const struct hmap *portno_names, struct ds *ds,
+ bool verbose)
+{
+ enum ovs_key_attr attr = nl_attr_type(a);
+ char namebuf[OVS_KEY_ATTR_BUFSIZE];
+ bool is_exact;
+
+ is_exact = ma ? odp_mask_attr_is_exact(ma) : true;
+
+ ds_put_cstr(ds, ovs_key_attr_to_string(attr, namebuf, sizeof namebuf));
+
+ if (!check_attr_len(ds, a, ma, ovs_flow_key_attr_lens,
+ OVS_KEY_ATTR_MAX, false)) {
+ return;
+ }
+
+ ds_put_char(ds, '(');
+ switch (attr) {
+ case OVS_KEY_ATTR_ENCAP:
+ if (ma && nl_attr_get_size(ma) && nl_attr_get_size(a)) {
+ odp_flow_format(nl_attr_get(a), nl_attr_get_size(a),
+ nl_attr_get(ma), nl_attr_get_size(ma), NULL, ds,
+ verbose);
+ } else if (nl_attr_get_size(a)) {
+ odp_flow_format(nl_attr_get(a), nl_attr_get_size(a), NULL, 0, NULL,
+ ds, verbose);
+ }
+ break;
+
+ case OVS_KEY_ATTR_PRIORITY:
+ case OVS_KEY_ATTR_SKB_MARK:
+ case OVS_KEY_ATTR_DP_HASH:
+ case OVS_KEY_ATTR_RECIRC_ID:
+ ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
+ if (!is_exact) {
+ ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
+ }
+ break;
+
+ case OVS_KEY_ATTR_CT_MARK:
+ if (verbose || !mask_empty(ma)) {
+ ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
+ if (!is_exact) {
+ ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
+ }
+ }
+ break;
+
+ case OVS_KEY_ATTR_CT_STATE:
+ if (verbose) {
+ ds_put_format(ds, "%#"PRIx32, nl_attr_get_u32(a));
+ if (!is_exact) {
+ ds_put_format(ds, "/%#"PRIx32,
+ mask_empty(ma) ? 0 : nl_attr_get_u32(ma));
+ }
+ } else if (!is_exact) {
+ format_flags_masked(ds, NULL, odp_ct_state_to_string,
+ nl_attr_get_u32(a),
+ mask_empty(ma) ? 0 : nl_attr_get_u32(ma),
+ UINT32_MAX);
+ } else {
+ format_flags(ds, odp_ct_state_to_string, nl_attr_get_u32(a), '|');
+ }
+ break;
+
+ case OVS_KEY_ATTR_CT_ZONE:
+ if (verbose || !mask_empty(ma)) {
+ ds_put_format(ds, "%#"PRIx16, nl_attr_get_u16(a));
+ if (!is_exact) {
+ ds_put_format(ds, "/%#"PRIx16, nl_attr_get_u16(ma));
+ }
+ }
+ break;
+
+ case OVS_KEY_ATTR_CT_LABELS: {
+ const ovs_u128 *value = nl_attr_get(a);
+ const ovs_u128 *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_u128(ds, value, mask, verbose);
+ break;
+ }
+
+ case OVS_KEY_ATTR_TUNNEL:
+ format_odp_tun_attr(a, ma, ds, verbose);
+ break;
+
+ case OVS_KEY_ATTR_IN_PORT:
+ if (portno_names && verbose && is_exact) {
+ char *name = odp_portno_names_get(portno_names,
+ u32_to_odp(nl_attr_get_u32(a)));
+ if (name) {
+ ds_put_format(ds, "%s", name);
+ } else {
+ ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
+ }
+ } else {
+ ds_put_format(ds, "%"PRIu32, nl_attr_get_u32(a));
+ if (!is_exact) {
+ ds_put_format(ds, "/%#"PRIx32, nl_attr_get_u32(ma));
+ }
+ }
+ break;
+
+ case OVS_KEY_ATTR_ETHERNET: {
+ const struct ovs_key_ethernet *mask = ma ? nl_attr_get(ma) : NULL;
+ const struct ovs_key_ethernet *key = nl_attr_get(a);
+
+ format_eth(ds, "src", key->eth_src, MASK(mask, eth_src), verbose);
+ format_eth(ds, "dst", key->eth_dst, MASK(mask, eth_dst), verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_VLAN:
+ format_vlan_tci(ds, nl_attr_get_be16(a),
+ ma ? nl_attr_get_be16(ma) : OVS_BE16_MAX, verbose);
+ break;
+
+ case OVS_KEY_ATTR_MPLS: {
+ const struct ovs_key_mpls *mpls_key = nl_attr_get(a);
+ const struct ovs_key_mpls *mpls_mask = NULL;
+ size_t size = nl_attr_get_size(a);
+
+ if (!size || size % sizeof *mpls_key) {
+ ds_put_format(ds, "(bad key length %"PRIuSIZE")", size);
+ return;
+ }
+ if (!is_exact) {
+ mpls_mask = nl_attr_get(ma);
+ if (size != nl_attr_get_size(ma)) {
+ ds_put_format(ds, "(key length %"PRIuSIZE" != "
+ "mask length %"PRIuSIZE")",
+ size, nl_attr_get_size(ma));
+ return;
+ }
+ }
+ format_mpls(ds, mpls_key, mpls_mask, size / sizeof *mpls_key);
+ break;
+ }
+ case OVS_KEY_ATTR_ETHERTYPE:
+ ds_put_format(ds, "0x%04"PRIx16, ntohs(nl_attr_get_be16(a)));
+ if (!is_exact) {
+ ds_put_format(ds, "/0x%04"PRIx16, ntohs(nl_attr_get_be16(ma)));
+ }
+ break;
+
+ case OVS_KEY_ATTR_IPV4: {
+ const struct ovs_key_ipv4 *key = nl_attr_get(a);
+ const struct ovs_key_ipv4 *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_ipv4(ds, "src", key->ipv4_src, MASK(mask, ipv4_src), verbose);
+ format_ipv4(ds, "dst", key->ipv4_dst, MASK(mask, ipv4_dst), verbose);
+ format_u8u(ds, "proto", key->ipv4_proto, MASK(mask, ipv4_proto),
+ verbose);
+ format_u8x(ds, "tos", key->ipv4_tos, MASK(mask, ipv4_tos), verbose);
+ format_u8u(ds, "ttl", key->ipv4_ttl, MASK(mask, ipv4_ttl), verbose);
+ format_frag(ds, "frag", key->ipv4_frag, MASK(mask, ipv4_frag),
+ verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_IPV6: {
+ const struct ovs_key_ipv6 *key = nl_attr_get(a);
+ const struct ovs_key_ipv6 *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_ipv6(ds, "src", key->ipv6_src, MASK(mask, ipv6_src), verbose);
+ format_ipv6(ds, "dst", key->ipv6_dst, MASK(mask, ipv6_dst), verbose);
+ format_ipv6_label(ds, "label", key->ipv6_label, MASK(mask, ipv6_label),
+ verbose);
+ format_u8u(ds, "proto", key->ipv6_proto, MASK(mask, ipv6_proto),
+ verbose);
+ format_u8x(ds, "tclass", key->ipv6_tclass, MASK(mask, ipv6_tclass),
+ verbose);
+ format_u8u(ds, "hlimit", key->ipv6_hlimit, MASK(mask, ipv6_hlimit),
+ verbose);
+ format_frag(ds, "frag", key->ipv6_frag, MASK(mask, ipv6_frag),
+ verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ /* These have the same structure and format. */
+ case OVS_KEY_ATTR_TCP:
+ case OVS_KEY_ATTR_UDP:
+ case OVS_KEY_ATTR_SCTP: {
+ const struct ovs_key_tcp *key = nl_attr_get(a);
+ const struct ovs_key_tcp *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_be16(ds, "src", key->tcp_src, MASK(mask, tcp_src), verbose);
+ format_be16(ds, "dst", key->tcp_dst, MASK(mask, tcp_dst), verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_TCP_FLAGS:
+ if (!is_exact) {
+ format_flags_masked(ds, NULL, packet_tcp_flag_to_string,
+ ntohs(nl_attr_get_be16(a)),
+ TCP_FLAGS(nl_attr_get_be16(ma)),
+ TCP_FLAGS(OVS_BE16_MAX));
+ } else {
+ format_flags(ds, packet_tcp_flag_to_string,
+ ntohs(nl_attr_get_be16(a)), '|');
+ }
+ break;
+
+ case OVS_KEY_ATTR_ICMP: {
+ const struct ovs_key_icmp *key = nl_attr_get(a);
+ const struct ovs_key_icmp *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_u8u(ds, "type", key->icmp_type, MASK(mask, icmp_type), verbose);
+ format_u8u(ds, "code", key->icmp_code, MASK(mask, icmp_code), verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_ICMPV6: {
+ const struct ovs_key_icmpv6 *key = nl_attr_get(a);
+ const struct ovs_key_icmpv6 *mask = ma ? nl_attr_get(ma) : NULL;
+
+ format_u8u(ds, "type", key->icmpv6_type, MASK(mask, icmpv6_type),
+ verbose);
+ format_u8u(ds, "code", key->icmpv6_code, MASK(mask, icmpv6_code),
+ verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_ARP: {
+ const struct ovs_key_arp *mask = ma ? nl_attr_get(ma) : NULL;
+ const struct ovs_key_arp *key = nl_attr_get(a);
+
+ format_ipv4(ds, "sip", key->arp_sip, MASK(mask, arp_sip), verbose);
+ format_ipv4(ds, "tip", key->arp_tip, MASK(mask, arp_tip), verbose);
+ format_be16(ds, "op", key->arp_op, MASK(mask, arp_op), verbose);
+ format_eth(ds, "sha", key->arp_sha, MASK(mask, arp_sha), verbose);
+ format_eth(ds, "tha", key->arp_tha, MASK(mask, arp_tha), verbose);
+ ds_chomp(ds, ',');
+ break;
+ }
+ case OVS_KEY_ATTR_ND: {
+ const struct ovs_key_nd *mask = ma ? nl_attr_get(ma) : NULL;
+ const struct ovs_key_nd *key = nl_attr_get(a);
+
+ format_ipv6(ds, "target", key->nd_target, MASK(mask, nd_target),
+ verbose);
+ format_eth(ds, "sll", key->nd_sll, MASK(mask, nd_sll), verbose);
+ format_eth(ds, "tll", key->nd_tll, MASK(mask, nd_tll), verbose);
+
+ ds_chomp(ds, ',');