diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h index 5806aba93..bc6c94b8d 100644 --- a/datapath/linux/compat/include/linux/openvswitch.h +++ b/datapath/linux/compat/include/linux/openvswitch.h @@ -793,6 +793,25 @@ struct ovs_action_push_eth { struct ovs_key_ethernet addresses; }; +#define OVS_ENCAP_NSH_MAX_MD_LEN 16 +/* + * struct ovs_action_encap_nsh - %OVS_ACTION_ATTR_ENCAP_NSH + * @flags: NSH header flags. + * @mdtype: NSH metadata type. + * @mdlen: Length of NSH metadata in bytes. + * @np: NSH next_protocol: Inner packet type. + * @path_hdr: NSH service path id and service index. + * @metadata: NSH metadata for MD type 1 or 2 + */ +struct ovs_action_encap_nsh { + uint8_t flags; + uint8_t mdtype; + uint8_t mdlen; + uint8_t np; + __be32 path_hdr; + uint8_t metadata[OVS_ENCAP_NSH_MAX_MD_LEN]; +}; + /** * enum ovs_nat_attr - Attributes for %OVS_CT_ATTR_NAT. * @@ -868,6 +887,8 @@ enum ovs_nat_attr { * @OVS_ACTION_ATTR_PUSH_ETH: Push a new outermost Ethernet header onto the * packet. * @OVS_ACTION_ATTR_POP_ETH: Pop the outermost Ethernet header off the packet. + * @OVS_ACTION_ATTR_ENCAP_NSH: encap NSH action to push NSH header. + * @OVS_ACTION_ATTR_DECAP_NSH: decap NSH action to remove NSH header. * * Only a single header can be set with a single %OVS_ACTION_ATTR_SET. Not all * fields within a header are modifiable, e.g. the IPv4 protocol and fragment @@ -909,6 +930,8 @@ enum ovs_action_attr { OVS_ACTION_ATTR_TUNNEL_POP, /* u32 port number. */ OVS_ACTION_ATTR_CLONE, /* Nested OVS_CLONE_ATTR_*. */ OVS_ACTION_ATTR_METER, /* u32 meter number. */ + OVS_ACTION_ATTR_ENCAP_NSH, /* struct ovs_action_encap_nsh. */ + OVS_ACTION_ATTR_DECAP_NSH, /* No argument. */ #endif __OVS_ACTION_ATTR_MAX, /* Nothing past this will be accepted * from userspace. */ diff --git a/include/openvswitch/ofp-ed-props.h b/include/openvswitch/ofp-ed-props.h index cf2fa628b..306c6fe73 100644 --- a/include/openvswitch/ofp-ed-props.h +++ b/include/openvswitch/ofp-ed-props.h @@ -27,9 +27,10 @@ extern "C" { enum ofp_ed_prop_class { OFPPPC_BASIC = 0, /* ONF Basic class. */ - OFPPPC_MPLS = 1, /* MPLS property class. */ - OFPPPC_GRE = 2, /* GRE property class. */ - OFPPPC_GTP = 3, /* GTP property class. */ + OFPPPC_MPLS = 1, /* MPLS property class. */ + OFPPPC_GRE = 2, /* GRE property class. */ + OFPPPC_GTP = 3, /* GTP property class. */ + OFPPPC_NSH = 4, /* NSH property class */ /* Experimenter property class. * @@ -39,6 +40,12 @@ enum ofp_ed_prop_class { OFPPPC_EXPERIMENTER=0xffff }; +enum ofp_ed_nsh_prop_type { + OFPPPT_PROP_NSH_NONE = 0, /* unused */ + OFPPPT_PROP_NSH_MDTYPE = 1, /* property MDTYPE in NSH */ + OFPPPT_PROP_NSH_TLV = 2, /* property TLV in NSH */ +}; + /* * External representation of encap/decap properties. * These must be padded to a multiple of 8 bytes. @@ -49,6 +56,22 @@ struct ofp_ed_prop_header { uint8_t len; }; +struct ofp_ed_prop_nsh_md_type { + struct ofp_ed_prop_header header; + uint8_t md_type; /* NSH MD type .*/ + uint8_t pad[3]; /* Padding to 8 bytes. */ +}; + +struct ofp_ed_prop_nsh_tlv { + struct ofp_ed_prop_header header; + ovs_be16 tlv_class; /* Metadata class. */ + uint8_t tlv_type; /* Metadata type including C bit. */ + uint8_t tlv_len; /* Metadata value length (0-127). */ + + /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */ + uint8_t data[0]; +}; + /* * Internal representation of encap/decap properties */ @@ -58,6 +81,21 @@ struct ofpact_ed_prop { uint8_t len; }; +struct ofpact_ed_prop_nsh_md_type { + struct ofpact_ed_prop header; + uint8_t md_type; /* NSH MD type .*/ + uint8_t pad[3]; /* Padding to 8 bytes. */ +}; + +struct ofpact_ed_prop_nsh_tlv { + struct ofpact_ed_prop header; + ovs_be16 tlv_class; /* Metadata class. */ + uint8_t tlv_type; /* Metadata type including C bit. */ + uint8_t tlv_len; /* Metadata value length (0-127). */ + + /* tlv_len octets of metadata value, padded to a multiple of 8 bytes. */ + uint8_t data[0]; +}; enum ofperr decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop, struct ofpbuf *out, size_t *remaining); enum ofperr encode_ed_prop(const struct ofpact_ed_prop **prop, diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c index 8ecc9d134..e2cd9310d 100644 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@ -5407,6 +5407,8 @@ dp_execute_cb(void *aux_, struct dp_packet_batch *packets_, case OVS_ACTION_ATTR_PUSH_ETH: case OVS_ACTION_ATTR_POP_ETH: case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: case __OVS_ACTION_ATTR_MAX: OVS_NOT_REACHED(); } diff --git a/lib/dpif.c b/lib/dpif.c index 1af53f5be..e71f6a3d1 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -1255,6 +1255,8 @@ dpif_execute_helper_cb(void *aux_, struct dp_packet_batch *packets_, case OVS_ACTION_ATTR_PUSH_ETH: case OVS_ACTION_ATTR_POP_ETH: case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: OVS_NOT_REACHED(); diff --git a/lib/odp-execute.c b/lib/odp-execute.c index e631c6836..5f4d23a91 100644 --- a/lib/odp-execute.c +++ b/lib/odp-execute.c @@ -289,10 +289,9 @@ odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key, } break; case NSH_M_TYPE2: - /* TODO */ - break; default: - OVS_NOT_REACHED(); + /* No support for setting any other metadata format yet. */ + break; } } else { uint8_t flags = (ntohs(nsh->ver_flags_len) & NSH_FLAGS_MASK) >> @@ -314,10 +313,9 @@ odp_set_nsh(struct dp_packet *packet, const struct ovs_key_nsh *key, } break; case NSH_M_TYPE2: - /* TODO */ - break; default: - OVS_NOT_REACHED(); + /* No support for setting any other metadata format yet. */ + break; } } } @@ -654,6 +652,8 @@ requires_datapath_assistance(const struct nlattr *a) case OVS_ACTION_ATTR_PUSH_ETH: case OVS_ACTION_ATTR_POP_ETH: case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: return false; case OVS_ACTION_ATTR_UNSPEC: @@ -818,6 +818,26 @@ odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal, } break; + case OVS_ACTION_ATTR_ENCAP_NSH: { + const struct ovs_action_encap_nsh *enc_nsh = nl_attr_get(a); + DP_PACKET_BATCH_FOR_EACH (packet, batch) { + encap_nsh(packet, enc_nsh); + } + break; + } + case OVS_ACTION_ATTR_DECAP_NSH: { + size_t i, num = batch->count; + + DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) { + if (decap_nsh(packet)) { + dp_packet_batch_refill(batch, packet, i); + } else { + dp_packet_delete(packet); + } + } + break; + } + case OVS_ACTION_ATTR_OUTPUT: case OVS_ACTION_ATTR_TUNNEL_PUSH: case OVS_ACTION_ATTR_TUNNEL_POP: diff --git a/lib/odp-util.c b/lib/odp-util.c index fa4871c4b..ef8b39db0 100644 --- a/lib/odp-util.c +++ b/lib/odp-util.c @@ -129,6 +129,8 @@ odp_action_len(uint16_t type) case OVS_ACTION_ATTR_PUSH_ETH: return sizeof(struct ovs_action_push_eth); case OVS_ACTION_ATTR_POP_ETH: return 0; case OVS_ACTION_ATTR_CLONE: return ATTR_LEN_VARIABLE; + case OVS_ACTION_ATTR_ENCAP_NSH: return ATTR_LEN_VARIABLE; + case OVS_ACTION_ATTR_DECAP_NSH: return 0; case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: @@ -266,10 +268,9 @@ format_nsh_key(struct ds *ds, const struct ovs_key_nsh *key) } break; case NSH_M_TYPE2: - /* TODO */ - break; default: - OVS_NOT_REACHED(); + /* No support for matching other metadata formats yet. */ + break; } } @@ -340,6 +341,40 @@ format_nsh_key_mask(struct ds *ds, const struct ovs_key_nsh *key, } } +static void +format_odp_encap_nsh_action(struct ds *ds, + const struct ovs_action_encap_nsh *encap_nsh) + { + uint32_t path_hdr = ntohl(encap_nsh->path_hdr); + uint32_t spi = (path_hdr & NSH_SPI_MASK) >> NSH_SPI_SHIFT; + uint8_t si = (path_hdr & NSH_SI_MASK) >> NSH_SI_SHIFT; + + ds_put_cstr(ds, "encap_nsh("); + ds_put_format(ds, "flags=%d", encap_nsh->flags); + ds_put_format(ds, ",mdtype=%d", encap_nsh->mdtype); + ds_put_format(ds, ",np=%d", encap_nsh->np); + ds_put_format(ds, ",spi=0x%x", spi); + ds_put_format(ds, ",si=%d", si); + switch (encap_nsh->mdtype) { + case NSH_M_TYPE1: { + struct nsh_md1_ctx *md1_ctx = + ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh->metadata); + for (int i = 0; i < 4; i++) { + ds_put_format(ds, ",c%d=0x%x", i + 1, + ntohl(get_16aligned_be32(&md1_ctx->c[i]))); + } + break; + } + case NSH_M_TYPE2: + ds_put_cstr(ds, ",md2="); + ds_put_hex(ds, encap_nsh->metadata, encap_nsh->mdlen); + break; + default: + OVS_NOT_REACHED(); + } + ds_put_format(ds, ")"); +} + static const char * slow_path_reason_to_string(uint32_t reason) { @@ -635,7 +670,7 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data) gnh->oam ? "oam," : "", gnh->critical ? "crit," : "", ntohl(get_16aligned_be32(&gnh->vni)) >> 8); - + if (gnh->opt_len) { ds_put_cstr(ds, ",options("); format_geneve_opts(gnh->options, NULL, gnh->opt_len * 4, @@ -1022,6 +1057,12 @@ format_odp_action(struct ds *ds, const struct nlattr *a, case OVS_ACTION_ATTR_CLONE: format_odp_clone_action(ds, a, portno_names); break; + case OVS_ACTION_ATTR_ENCAP_NSH: + format_odp_encap_nsh_action(ds, nl_attr_get(a)); + break; + case OVS_ACTION_ATTR_DECAP_NSH: + ds_put_cstr(ds, "decap_nsh()"); + break; case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: default: @@ -1739,6 +1780,113 @@ find_end: return s - s_; } +static int +parse_odp_encap_nsh_action(const char *s, struct ofpbuf *actions) +{ + int n = 0; + int ret = 0; + struct ovs_action_encap_nsh encap_nsh; + uint32_t spi; + uint8_t si; + uint32_t cd; + + if (!ovs_scan_len(s, &n, "encap_nsh(")) { + ret = -EINVAL; + goto out; + } + + /* The default is NSH_M_TYPE1 */ + encap_nsh.flags = 0; + encap_nsh.mdtype = NSH_M_TYPE1; + encap_nsh.mdlen = NSH_M_TYPE1_MDLEN; + encap_nsh.path_hdr = htonl(255); + memset(encap_nsh.metadata, 0, NSH_M_TYPE1_MDLEN); + + for (;;) { + n += strspn(s + n, delimiters); + if (s[n] == ')') { + break; + } + + if (ovs_scan_len(s, &n, "flags=%"SCNi8, &encap_nsh.flags)) { + continue; + } + if (ovs_scan_len(s, &n, "mdtype=%"SCNi8, &encap_nsh.mdtype)) { + switch (encap_nsh.mdtype) { + case NSH_M_TYPE1: + /* This is the default format. */; + break; + case NSH_M_TYPE2: + /* Length will be updated later. */ + encap_nsh.mdlen = 0; + break; + default: + ret = -EINVAL; + goto out; + } + continue; + } + if (ovs_scan_len(s, &n, "np=%"SCNi8, &encap_nsh.np)) { + continue; + } + if (ovs_scan_len(s, &n, "spi=0x%"SCNx32, &spi)) { + encap_nsh.path_hdr = + htonl(((spi << NSH_SPI_SHIFT) & NSH_SPI_MASK) | + (ntohl(encap_nsh.path_hdr) & ~NSH_SPI_MASK)); + continue; + } + if (ovs_scan_len(s, &n, "si=%"SCNi8, &si)) { + encap_nsh.path_hdr = + htonl((si << NSH_SI_SHIFT) | + (ntohl(encap_nsh.path_hdr) & ~NSH_SI_MASK)); + continue; + } + if (encap_nsh.mdtype == NSH_M_TYPE1) { + struct nsh_md1_ctx *md1 = + ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata); + if (ovs_scan_len(s, &n, "c1=0x%"SCNx32, &cd)) { + put_16aligned_be32(&md1->c[0], htonl(cd)); + continue; + } + if (ovs_scan_len(s, &n, "c2=0x%"SCNx32, &cd)) { + put_16aligned_be32(&md1->c[1], htonl(cd)); + continue; + } + if (ovs_scan_len(s, &n, "c3=0x%"SCNx32, &cd)) { + put_16aligned_be32(&md1->c[2], htonl(cd)); + continue; + } + if (ovs_scan_len(s, &n, "c4=0x%"SCNx32, &cd)) { + put_16aligned_be32(&md1->c[3], htonl(cd)); + continue; + } + } + else if (encap_nsh.mdtype == NSH_M_TYPE2) { + struct ofpbuf b; + char buf[512]; + size_t mdlen; + if (ovs_scan_len(s, &n, "md2=0x%511[0-9a-fA-F]", buf)) { + ofpbuf_use_stub(&b, encap_nsh.metadata, + OVS_ENCAP_NSH_MAX_MD_LEN); + ofpbuf_put_hex(&b, buf, &mdlen); + encap_nsh.mdlen = mdlen; + ofpbuf_uninit(&b); + } + continue; + } + } +out: + if (ret < 0) { + return ret; + } else { + size_t size = offsetof(struct ovs_action_encap_nsh, metadata) + + ROUND_UP(encap_nsh.mdlen, 4); + nl_msg_put_unspec(actions, OVS_ACTION_ATTR_ENCAP_NSH, + &encap_nsh, size); + return n; + } +} + static int parse_action_list(const char *s, const struct simap *port_names, struct ofpbuf *actions) @@ -1940,6 +2088,24 @@ parse_odp_action(const char *s, const struct simap *port_names, } } + { + if (!strncmp(s, "encap_nsh(", 10)) { + int retval = parse_odp_encap_nsh_action(s, actions); + if (retval < 0) { + return retval; + } + return retval + 1; + } + } + + { + int n; + if (ovs_scan(s, "decap_nsh()%n", &n)) { + nl_msg_put_flag(actions, OVS_ACTION_ATTR_DECAP_NSH); + return n; + } + } + { uint32_t port; int n; @@ -2454,6 +2620,7 @@ format_eth(struct ds *ds, const char *name, const struct eth_addr key, } } + static void format_be64(struct ds *ds, const char *name, ovs_be64 key, const ovs_be64 *mask, bool verbose) @@ -6463,7 +6630,8 @@ get_nsh_key(const struct flow *flow, struct ovs_key_nsh *nsh, bool is_mask) } break; case NSH_M_TYPE2: - /* TODO: MD type 2 */ + default: + /* No match support for other MD formats yet. */ break; } } @@ -6486,7 +6654,8 @@ put_nsh_key(const struct ovs_key_nsh *nsh, struct flow *flow, } break; case NSH_M_TYPE2: - /* TODO: MD type 2 */ + default: + /* No match support for other MD formats yet. */ memset(flow->nsh.c, 0, sizeof flow->nsh.c); break; } @@ -6618,12 +6787,59 @@ commit_set_pkt_mark_action(const struct flow *flow, struct flow *base_flow, } } +static void +odp_put_decap_nsh_action(struct ofpbuf *odp_actions) +{ + nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_DECAP_NSH); +} + +static void +odp_put_encap_nsh_action(struct ofpbuf *odp_actions, + const struct flow *flow, + struct ofpbuf *encap_data) +{ + struct ovs_action_encap_nsh encap_nsh; + + encap_nsh.flags = flow->nsh.flags; + encap_nsh.mdtype = flow->nsh.mdtype; + encap_nsh.np = flow->nsh.np; + encap_nsh.path_hdr = htonl((ntohl(flow->nsh.spi) << NSH_SPI_SHIFT) | + flow->nsh.si); + + switch (encap_nsh.mdtype) { + case NSH_M_TYPE1: { + struct nsh_md1_ctx *md1 = + ALIGNED_CAST(struct nsh_md1_ctx *, encap_nsh.metadata); + encap_nsh.mdlen = NSH_M_TYPE1_MDLEN; + for (int i = 0; i < 4; i++) { + put_16aligned_be32(&md1->c[i], flow->nsh.c[i]); + } + break; + } + case NSH_M_TYPE2: + if (encap_data) { + ovs_assert(encap_data->size < OVS_ENCAP_NSH_MAX_MD_LEN); + encap_nsh.mdlen = encap_data->size; + memcpy(encap_nsh.metadata, encap_data->data, encap_data->size); + } else { + encap_nsh.mdlen = 0; + } + break; + default: + encap_nsh.mdlen = 0; + break; + } + nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_ENCAP_NSH, + &encap_nsh, sizeof(encap_nsh)); +} + static void commit_packet_type_change(const struct flow *flow, struct flow *base_flow, struct ofpbuf *odp_actions, struct flow_wildcards *wc, - bool pending_encap) + bool pending_encap, + struct ofpbuf *encap_data) { if (flow->packet_type == base_flow->packet_type) { return; @@ -6640,22 +6856,40 @@ commit_packet_type_change(const struct flow *flow, base_flow->dl_dst = flow->dl_dst; break; } + case PT_NSH: + /* encap_nsh */ + odp_put_encap_nsh_action(odp_actions, flow, encap_data); + base_flow->packet_type = flow->packet_type; + /* Update all packet headers in base_flow. */ + memcpy(&base_flow->dl_dst, &flow->dl_dst, + sizeof(*flow) - offsetof(struct flow, dl_dst)); + break; default: - /* Only the above protocols are supported for encap. The check - * is done at action decoding. */ + /* Only the above protocols are supported for encap. + * The check is done at action translation. */ OVS_NOT_REACHED(); } } else { + /* This is an explicit or implicit decap case. */ if (pt_ns(flow->packet_type) == OFPHTN_ETHERTYPE && base_flow->packet_type == htonl(PT_ETH)) { - /* pop_eth */ + /* Generate pop_eth and continue without recirculation. */ odp_put_pop_eth_action(odp_actions); base_flow->packet_type = flow->packet_type; base_flow->dl_src = eth_addr_zero; base_flow->dl_dst = eth_addr_zero; } else { - /* All other cases are handled through recirculation. */ - OVS_NOT_REACHED(); + /* All other decap cases require recirculation. + * No need to update the base flow here. */ + switch (ntohl(base_flow->packet_type)) { + case PT_NSH: + /* decap_nsh. */ + odp_put_decap_nsh_action(odp_actions); + break; + default: + /* Checks are done during translation. */ + OVS_NOT_REACHED(); + } } } @@ -6674,12 +6908,14 @@ commit_packet_type_change(const struct flow *flow, enum slow_path_reason commit_odp_actions(const struct flow *flow, struct flow *base, struct ofpbuf *odp_actions, struct flow_wildcards *wc, - bool use_masked, bool pending_encap) + bool use_masked, bool pending_encap, + struct ofpbuf *encap_data) { enum slow_path_reason slow1, slow2; bool mpls_done = false; - commit_packet_type_change(flow, base, odp_actions, wc, pending_encap); + commit_packet_type_change(flow, base, odp_actions, wc, + pending_encap, encap_data); commit_set_ether_action(flow, base, odp_actions, wc, use_masked); /* Make packet a non-MPLS packet before committing L3/4 actions, * which would otherwise do nothing. */ diff --git a/lib/odp-util.h b/lib/odp-util.h index 111cd9cff..27c2ab4f0 100644 --- a/lib/odp-util.h +++ b/lib/odp-util.h @@ -277,7 +277,8 @@ enum slow_path_reason commit_odp_actions(const struct flow *, struct ofpbuf *odp_actions, struct flow_wildcards *wc, bool use_masked, - bool pending_encap); + bool pending_encap, + struct ofpbuf *encap_data); /* ofproto-dpif interface. * diff --git a/lib/ofp-actions.c b/lib/ofp-actions.c index 70765377e..bfc8a805f 100644 --- a/lib/ofp-actions.c +++ b/lib/ofp-actions.c @@ -4055,6 +4055,7 @@ decode_NXAST_RAW_ENCAP(const struct nx_action_encap *nae, encap->ofpact.raw = NXAST_RAW_ENCAP; switch (ntohl(nae->new_pkt_type)) { case PT_ETH: + case PT_NSH: /* Add supported encap header types here. */ break; default: @@ -4103,6 +4104,8 @@ parse_encap_header(const char *hdr, ovs_be32 *packet_type) { if (strcmp(hdr, "ethernet") == 0) { *packet_type = htonl(PT_ETH); + } else if (strcmp(hdr, "nsh") == 0) { + *packet_type = htonl(PT_NSH); } else { return false; } @@ -4178,6 +4181,8 @@ format_encap_pkt_type(const ovs_be32 pkt_type) switch (ntohl(pkt_type)) { case PT_ETH: return "ethernet"; + case PT_NSH: + return "nsh"; default: return "UNKNOWN"; } diff --git a/lib/ofp-ed-props.c b/lib/ofp-ed-props.c index a34613868..eb168fee3 100644 --- a/lib/ofp-ed-props.c +++ b/lib/ofp-ed-props.c @@ -30,6 +30,7 @@ decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop, size_t *remaining) { uint16_t prop_class = ntohs((*ofp_prop)->prop_class); + uint8_t prop_type = (*ofp_prop)->type; size_t len = (*ofp_prop)->len; size_t pad_len = ROUND_UP(len, 8); @@ -38,6 +39,45 @@ decode_ed_prop(const struct ofp_ed_prop_header **ofp_prop, } switch (prop_class) { + case OFPPPC_NSH: { + switch (prop_type) { + case OFPPPT_PROP_NSH_MDTYPE: { + struct ofp_ed_prop_nsh_md_type *opnmt = + ALIGNED_CAST(struct ofp_ed_prop_nsh_md_type *, *ofp_prop); + if (len > sizeof(*opnmt) || len > *remaining) { + return OFPERR_NXBAC_BAD_ED_PROP; + } + struct ofpact_ed_prop_nsh_md_type *pnmt = + ofpbuf_put_uninit(out, sizeof(*pnmt)); + pnmt->header.prop_class = prop_class; + pnmt->header.type = prop_type; + pnmt->header.len = len; + pnmt->md_type = opnmt->md_type; + break; + } + case OFPPPT_PROP_NSH_TLV: { + struct ofp_ed_prop_nsh_tlv *opnt = + ALIGNED_CAST(struct ofp_ed_prop_nsh_tlv *, *ofp_prop); + size_t tlv_pad_len = ROUND_UP(opnt->tlv_len, 8); + if (len != sizeof(*opnt) + tlv_pad_len || len > *remaining) { + return OFPERR_NXBAC_BAD_ED_PROP; + } + struct ofpact_ed_prop_nsh_tlv *pnt = + ofpbuf_put_uninit(out, sizeof(*pnt)); + pnt->header.prop_class = prop_class; + pnt->header.type = prop_type; + pnt->header.len = len; + pnt->tlv_class = opnt->tlv_class; + pnt->tlv_type = opnt->tlv_type; + pnt->tlv_len = opnt->tlv_len; + ofpbuf_put(out, opnt->data, tlv_pad_len); + break; + } + default: + return OFPERR_NXBAC_UNKNOWN_ED_PROP; + } + break; + } default: return OFPERR_NXBAC_UNKNOWN_ED_PROP; } @@ -55,6 +95,43 @@ encode_ed_prop(const struct ofpact_ed_prop **prop, size_t prop_len; switch ((*prop)->prop_class) { + case OFPPPC_NSH: { + switch ((*prop)->type) { + case OFPPPT_PROP_NSH_MDTYPE: { + struct ofpact_ed_prop_nsh_md_type *pnmt = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_md_type *, *prop); + struct ofp_ed_prop_nsh_md_type *opnmt = + ofpbuf_put_uninit(out, sizeof(*opnmt)); + opnmt->header.prop_class = htons((*prop)->prop_class); + opnmt->header.type = (*prop)->type; + opnmt->header.len = + offsetof(struct ofp_ed_prop_nsh_md_type, pad); + opnmt->md_type = pnmt->md_type; + prop_len = sizeof(*pnmt); + break; + } + case OFPPPT_PROP_NSH_TLV: { + struct ofpact_ed_prop_nsh_tlv *pnt = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, *prop); + struct ofp_ed_prop_nsh_tlv *opnt; + size_t tlv_pad_len = ROUND_UP(pnt->tlv_len, 8); + size_t len = sizeof(*opnt) + tlv_pad_len; + opnt = ofpbuf_put_uninit(out, len); + opnt->header.prop_class = htons((*prop)->prop_class); + opnt->header.type = (*prop)->type; + opnt->header.len = len; + opnt->tlv_class = pnt->tlv_class; + opnt->tlv_type = pnt->tlv_type; + opnt->tlv_len = pnt->tlv_len; + memcpy(opnt->data, pnt->data, tlv_pad_len); + prop_len = sizeof(*pnt) + tlv_pad_len; + break; + } + default: + return OFPERR_OFPBAC_BAD_ARGUMENT; + } + break; + } default: return OFPERR_OFPBAC_BAD_ARGUMENT; } @@ -78,6 +155,8 @@ parse_ed_prop_class(const char *str OVS_UNUSED, *prop_class = OFPPPC_GRE; } else if (!strcmp(str,"gtp")) { *prop_class = OFPPPC_GTP; + } else if (!strcmp(str,"nsh")) { + *prop_class = OFPPPC_NSH; } else { return false; } @@ -90,6 +169,16 @@ parse_ed_prop_type(uint16_t prop_class, uint8_t *type OVS_UNUSED) { switch (prop_class) { + case OFPPPC_NSH: + if (!strcmp(str, "md_type")) { + *type = OFPPPT_PROP_NSH_MDTYPE; + return true; + } else if (!strcmp(str, "tlv")) { + *type = OFPPPT_PROP_NSH_TLV; + return true; + } else { + return false; + } default: return false; } @@ -106,12 +195,68 @@ char * parse_ed_prop_value(uint16_t prop_class, uint8_t prop_type OVS_UNUSED, const char *value, struct ofpbuf *out OVS_UNUSED) { + char *error = NULL; if (value == NULL || *value == '\0') { return xstrdup("Value missing for encap property"); } switch (prop_class) { + case OFPPPC_NSH: + switch (prop_type) { + case OFPPPT_PROP_NSH_MDTYPE: { + /* Format: ":uint8_t". */ + uint8_t md_type; + error = str_to_u8(value, "md_type", &md_type); + if (error != NULL) { + return error; + } + if (md_type < 1 || md_type > 2) { + return xstrdup("invalid md_type"); + } + struct ofpact_ed_prop_nsh_md_type *pnmt = + ofpbuf_put_uninit(out, sizeof(*pnmt)); + pnmt->header.prop_class = prop_class; + pnmt->header.type = prop_type; + pnmt->header.len = + offsetof(struct ofp_ed_prop_nsh_md_type, pad); + pnmt->md_type = md_type; + break; + } + case OFPPPT_PROP_NSH_TLV: { + /* Format: ":ovs_be16,:uint8_t,:hex_string" */ + struct ofpact_ed_prop_nsh_tlv *pnt; + uint16_t tlv_class; + uint8_t tlv_type; + char buf[256]; + size_t tlv_value_len, padding; + size_t start_ofs = out->size; + + if (!ovs_scan(value, "0x%"SCNx16",%"SCNu8",0x%251[0-9a-fA-F]", + &tlv_class, &tlv_type, buf)) { + return xasprintf("Invalid NSH TLV header: %s", value); + } + ofpbuf_put_uninit(out, sizeof(*pnt)); + ofpbuf_put_hex(out, buf, &tlv_value_len); + pnt = ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, + ((char *)out->data + start_ofs)); + padding = ROUND_UP(tlv_value_len, 8) - tlv_value_len; + pnt->header.prop_class = prop_class; + pnt->header.type = prop_type; + pnt->header.len = sizeof(*pnt) + tlv_value_len + padding; + pnt->tlv_class = htons(tlv_class); + pnt->tlv_type = tlv_type; + pnt->tlv_len = tlv_value_len; + if (padding > 0) { + ofpbuf_put_zeros(out, padding); + } + break; + } + default: + /* Unsupported property types rejected before. */ + OVS_NOT_REACHED(); + } + break; default: /* Unsupported property classes rejected before. */ OVS_NOT_REACHED(); @@ -132,6 +277,8 @@ format_ed_prop_class(const struct ofpact_ed_prop *prop) return "gre"; case OFPPPC_GTP: return "gtp"; + case OFPPPC_NSH: + return "nsh"; default: OVS_NOT_REACHED(); } @@ -141,6 +288,16 @@ char * format_ed_prop_type(const struct ofpact_ed_prop *prop) { switch (prop->prop_class) { + case OFPPPC_NSH: + switch (prop->type) { + case OFPPPT_PROP_NSH_MDTYPE: + return "md_type"; + case OFPPPT_PROP_NSH_TLV: + return "tlv"; + default: + OVS_NOT_REACHED(); + } + break; default: OVS_NOT_REACHED(); } @@ -151,6 +308,28 @@ format_ed_prop(struct ds *s OVS_UNUSED, const struct ofpact_ed_prop *prop) { switch (prop->prop_class) { + case OFPPPC_NSH: + switch (prop->type) { + case OFPPPT_PROP_NSH_MDTYPE: { + struct ofpact_ed_prop_nsh_md_type *pnmt = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_md_type *, prop); + ds_put_format(s, "%s=%d", format_ed_prop_type(prop), + pnmt->md_type); + return; + } + case OFPPPT_PROP_NSH_TLV: { + struct ofpact_ed_prop_nsh_tlv *pnt = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, prop); + ds_put_format(s, "%s(0x%04x,%d,", + format_ed_prop_type(prop), + ntohs(pnt->tlv_class), pnt->tlv_type); + ds_put_hex(s, pnt->data, pnt->tlv_len); + ds_put_cstr(s,")"); + return; + } + default: + OVS_NOT_REACHED(); + } default: OVS_NOT_REACHED(); } diff --git a/lib/packets.c b/lib/packets.c index 7a9071c0c..a304853de 100644 --- a/lib/packets.c +++ b/lib/packets.c @@ -402,6 +402,88 @@ pop_mpls(struct dp_packet *packet, ovs_be16 ethtype) } } +void +encap_nsh(struct dp_packet *packet, const struct ovs_action_encap_nsh *encap) +{ + struct nsh_hdr *nsh; + size_t length = NSH_BASE_HDR_LEN + encap->mdlen; + uint8_t next_proto; + + switch (ntohl(packet->packet_type)) { + case PT_ETH: + next_proto = NSH_P_ETHERNET; + break; + case PT_IPV4: + next_proto = NSH_P_IPV4; + break; + case PT_IPV6: + next_proto = NSH_P_IPV6; + break; + case PT_NSH: + next_proto = NSH_P_NSH; + break; + default: + OVS_NOT_REACHED(); + } + + nsh = (struct nsh_hdr *) dp_packet_push_uninit(packet, length); + nsh->ver_flags_len = htons(encap->flags << NSH_FLAGS_SHIFT | length >> 2); + nsh->next_proto = next_proto; + put_16aligned_be32(&nsh->path_hdr, encap->path_hdr); + nsh->md_type = encap->mdtype; + switch (nsh->md_type) { + case NSH_M_TYPE1: + nsh->md1 = *ALIGNED_CAST(struct nsh_md1_ctx *, encap->metadata); + break; + case NSH_M_TYPE2: { + /* The MD2 metadata in encap is already padded to 4 bytes. */ + size_t len = ROUND_UP(encap->mdlen, 4); + memcpy(nsh->md2, encap->metadata, len); + break; + } + default: + OVS_NOT_REACHED(); + } + + packet->packet_type = htonl(PT_NSH); + dp_packet_reset_offsets(packet); + packet->l3_ofs = 0; +} + +bool +decap_nsh(struct dp_packet *packet) +{ + struct nsh_hdr *nsh = (struct nsh_hdr *) dp_packet_l3(packet); + size_t length; + uint32_t next_pt; + + if (packet->packet_type == htonl(PT_NSH) && nsh) { + switch (nsh->next_proto) { + case NSH_P_ETHERNET: + next_pt = PT_ETH; + break; + case NSH_P_IPV4: + next_pt = PT_IPV4; + break; + case NSH_P_IPV6: + next_pt = PT_IPV6; + break; + case NSH_P_NSH: + next_pt = PT_NSH; + break; + default: + /* Unknown inner packet type. Drop packet. */ + return false; + } + + length = nsh_hdr_len(nsh); + dp_packet_reset_packet(packet, length); + packet->packet_type = htonl(next_pt); + /* Packet must be recirculated for further processing. */ + } + return true; +} + /* Converts hex digits in 'hex' to an Ethernet packet in '*packetp'. The * caller must free '*packetp'. On success, returns NULL. On failure, returns * an error message and stores NULL in '*packetp'. diff --git a/lib/packets.h b/lib/packets.h index 5083e6a83..705d0b270 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -434,6 +434,10 @@ void push_eth(struct dp_packet *packet, const struct eth_addr *dst, const struct eth_addr *src); void pop_eth(struct dp_packet *packet); +void encap_nsh(struct dp_packet *packet, + const struct ovs_action_encap_nsh *encap_nsh); +bool decap_nsh(struct dp_packet *packet); + #define LLC_DSAP_SNAP 0xaa #define LLC_SSAP_SNAP 0xaa #define LLC_CNTL_SNAP 3 diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c index 605c20a3b..5549b4951 100644 --- a/ofproto/ofproto-dpif-ipfix.c +++ b/ofproto/ofproto-dpif-ipfix.c @@ -2824,6 +2824,8 @@ dpif_ipfix_read_actions(const struct flow *flow, case OVS_ACTION_ATTR_POP_MPLS: case OVS_ACTION_ATTR_PUSH_ETH: case OVS_ACTION_ATTR_POP_ETH: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: default: diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c index e4cca657d..65a2003a7 100644 --- a/ofproto/ofproto-dpif-sflow.c +++ b/ofproto/ofproto-dpif-sflow.c @@ -1199,6 +1199,8 @@ dpif_sflow_read_actions(const struct flow *flow, break; case OVS_ACTION_ATTR_SAMPLE: case OVS_ACTION_ATTR_CLONE: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: case OVS_ACTION_ATTR_UNSPEC: case __OVS_ACTION_ATTR_MAX: default: diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c index 79decdb9e..973e76054 100644 --- a/ofproto/ofproto-dpif-xlate.c +++ b/ofproto/ofproto-dpif-xlate.c @@ -55,6 +55,7 @@ #include "openvswitch/meta-flow.h" #include "openvswitch/list.h" #include "openvswitch/ofp-actions.h" +#include "openvswitch/ofp-ed-props.h" #include "openvswitch/vlog.h" #include "ovs-lldp.h" #include "ovs-router.h" @@ -234,8 +235,10 @@ struct xlate_ctx { bool in_action_set; /* Currently translating action_set, if true. */ bool in_packet_out; /* Currently translating a packet_out msg, if * true. */ - bool pending_encap; /* Waiting to commit a pending encap - * action, if true. */ + bool pending_encap; /* True when waiting to commit a pending + * encap action. */ + struct ofpbuf *encap_data; /* May contain a pointer to an ofpbuf with + * context for the datapath encap action.*/ uint8_t table_id; /* OpenFlow table ID where flow was found. */ ovs_be64 rule_cookie; /* Cookie of the rule being translated. */ @@ -3468,8 +3471,11 @@ xlate_commit_actions(struct xlate_ctx *ctx) ctx->xout->slow |= commit_odp_actions(&ctx->xin->flow, &ctx->base_flow, ctx->odp_actions, ctx->wc, - use_masked, ctx->pending_encap); + use_masked, ctx->pending_encap, + ctx->encap_data); ctx->pending_encap = false; + ofpbuf_delete(ctx->encap_data); + ctx->encap_data = NULL; } static void @@ -4386,6 +4392,8 @@ xlate_fixup_actions(struct ofpbuf *b, const struct nlattr *actions, case OVS_ACTION_ATTR_CT: case OVS_ACTION_ATTR_PUSH_ETH: case OVS_ACTION_ATTR_POP_ETH: + case OVS_ACTION_ATTR_ENCAP_NSH: + case OVS_ACTION_ATTR_DECAP_NSH: case OVS_ACTION_ATTR_METER: ofpbuf_put(b, a, nl_attr_len_pad(a, left)); break; @@ -5786,20 +5794,131 @@ rewrite_flow_encap_ethernet(struct xlate_ctx *ctx, flow->dl_dst = eth_addr_zero; flow->dl_type = ethertype; } else { + /* Error handling: drop packet. */ xlate_report_debug(ctx, OFT_ACTION, - "encap(ethernet) unsupported for packet type " - "ethernet"); - /* TODO: Error handling: drop packet. */ + "Dropping packet as encap(ethernet) is not " + "supported for packet type ethernet."); ctx->error = 1; } } +/* For an MD2 NSH header returns a pointer to an ofpbuf with the encoded + * MD2 TLVs provided as encap properties to the encap operation. This + * will be stored as encap_data in the ctx and copied into the encap_nsh + * action at the next commit. */ +static struct ofpbuf * +rewrite_flow_encap_nsh(struct xlate_ctx *ctx, + const struct ofpact_encap *encap, + struct flow *flow, + struct flow_wildcards *wc) +{ + ovs_be32 packet_type = flow->packet_type; + const char *ptr = (char *) encap->props; + struct ofpbuf *buf = ofpbuf_new(OVS_ENCAP_NSH_MAX_MD_LEN); + uint8_t md_type = NSH_M_TYPE1; + uint8_t np = 0; + int i; + + /* Scan the optional NSH encap TLV properties, if any. */ + for (i = 0; i < encap->n_props; i++) { + struct ofpact_ed_prop *prop_ptr = + ALIGNED_CAST(struct ofpact_ed_prop *, ptr); + if (prop_ptr->prop_class == OFPPPC_NSH) { + switch (prop_ptr->type) { + case OFPPPT_PROP_NSH_MDTYPE: { + struct ofpact_ed_prop_nsh_md_type *prop_md_type = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_md_type *, + prop_ptr); + md_type = prop_md_type->md_type; + break; + } + case OFPPPT_PROP_NSH_TLV: { + struct ofpact_ed_prop_nsh_tlv *tlv_prop = + ALIGNED_CAST(struct ofpact_ed_prop_nsh_tlv *, + prop_ptr); + struct nsh_md2_tlv *md2_ctx = + ofpbuf_put_uninit(buf, sizeof(*md2_ctx)); + md2_ctx->md_class = tlv_prop->tlv_class; + md2_ctx->type = tlv_prop->tlv_type; + md2_ctx->length = tlv_prop->tlv_len; + size_t len = ROUND_UP(md2_ctx->length, 4); + size_t padding = len - md2_ctx->length; + ofpbuf_put(buf, tlv_prop->data, md2_ctx->length); + ofpbuf_put_zeros(buf, padding); + break; + } + default: + /* No other NSH encap properties defined yet. */ + break; + } + } + ptr += ROUND_UP(prop_ptr->len, 8); + } + if (buf->size == 0 || buf->size > OVS_ENCAP_NSH_MAX_MD_LEN) { + ofpbuf_delete(buf); + buf = NULL; + } + + /* Determine the Next Protocol field for NSH header. */ + switch (ntohl(packet_type)) { + case PT_ETH: + np = NSH_P_ETHERNET; + break; + case PT_IPV4: + np = NSH_P_IPV4; + break; + case PT_IPV6: + np = NSH_P_IPV6; + break; + case PT_NSH: + np = NSH_P_NSH; + break; + default: + /* Error handling: drop packet. */ + xlate_report_debug(ctx, OFT_ACTION, + "Dropping packet as encap(nsh) is not " + "supported for packet type (%d,0x%x)", + pt_ns(packet_type), pt_ns_type(packet_type)); + ctx->error = 1; + return buf; + } + /* Note that we have matched on packet_type! */ + wc->masks.packet_type = OVS_BE32_MAX; + + /* Reset all current flow packet headers. */ + memset(&flow->dl_dst, 0, + sizeof(struct flow) - offsetof(struct flow, dl_dst)); + + /* Populate the flow with the new NSH header. */ + flow->packet_type = htonl(PT_NSH); + flow->dl_type = htons(ETH_TYPE_NSH); + flow->nsh.flags = 0; /* */ + flow->nsh.np = np; + flow->nsh.spi = 0; + flow->nsh.si = 255; + + if (md_type == NSH_M_TYPE1) { + flow->nsh.mdtype = NSH_M_TYPE1; + memset(flow->nsh.c, 0, sizeof flow->nsh.c); + if (buf) { + /* Drop any MD2 context TLVs. */ + ofpbuf_delete(buf); + buf = NULL; + } + } else if (md_type == NSH_M_TYPE2) { + flow->nsh.mdtype = NSH_M_TYPE2; + } + + return buf; +} + static void xlate_generic_encap_action(struct xlate_ctx *ctx, const struct ofpact_encap *encap) { struct flow *flow = &ctx->xin->flow; struct flow_wildcards *wc = ctx->wc; + struct ofpbuf *encap_data = NULL; /* Ensure that any pending actions on the inner packet are applied before * rewriting the flow */ @@ -5810,15 +5929,19 @@ xlate_generic_encap_action(struct xlate_ctx *ctx, case PT_ETH: rewrite_flow_encap_ethernet(ctx, flow, wc); break; + case PT_NSH: + encap_data = rewrite_flow_encap_nsh(ctx, encap, flow, wc); + break; default: - /* TODO: Error handling: Should not happen if the PT is checked - * at decoding */ + /* New packet type was checked during decoding. */ + OVS_NOT_REACHED(); break; } if (!ctx->error) { /* The actual encap datapath action will be generated at next commit. */ ctx->pending_encap = true; + ctx->encap_data = encap_data; } } @@ -5849,11 +5972,42 @@ xlate_generic_decap_action(struct xlate_ctx *ctx, ctx->wc->masks.dl_type = OVS_BE16_MAX; } return false; + case PT_NSH: + /* The decap_nsh action is generated at the commit executed as + * part of freezing the ctx for recirculation. Here we just set + * the new packet type based on the NSH next protocol field. */ + switch (flow->nsh.np) { + case NSH_P_ETHERNET: + flow->packet_type = htonl(PT_ETH); + break; + case NSH_P_IPV4: + flow->packet_type = htonl(PT_IPV4); + break; + case NSH_P_IPV6: + flow->packet_type = htonl(PT_IPV6); + break; + case NSH_P_NSH: + flow->packet_type = htonl(PT_NSH); + break; + default: + /* Error handling: drop packet. */ + xlate_report_debug(ctx, OFT_ACTION, + "Dropping packet as NSH next protocol %d " + "is not supported", flow->nsh.np); + ctx->error = 1; + return false; + break; + } + ctx->wc->masks.nsh.np = UINT8_MAX; + /* Trigger recirculation. */ + return true; default: - xlate_report_debug(ctx, OFT_ACTION, - "decap() for unsupported packet type %x", - ntohl(flow->packet_type)); - /* TODO: Error handling: drop packet. */ + /* Error handling: drop packet. */ + xlate_report_debug( + ctx, OFT_ACTION, + "Dropping packet as the decap() does not support " + "packet type (%d,0x%x)", + pt_ns(flow->packet_type), pt_ns_type(flow->packet_type)); ctx->error = 1; return false; } @@ -6699,6 +6853,7 @@ xlate_actions(struct xlate_in *xin, struct xlate_out *xout) .in_action_set = false, .in_packet_out = xin->in_packet_out, .pending_encap = false, + .encap_data = NULL, .table_id = 0, .rule_cookie = OVS_BE64_MAX, @@ -7046,6 +7201,7 @@ exit: ofpbuf_uninit(&ctx.action_set); ofpbuf_uninit(&ctx.frozen_actions); ofpbuf_uninit(&scratch_actions); + ofpbuf_delete(ctx.encap_data); /* Make sure we return a "drop flow" in case of an error. */ if (ctx.error) {