#include <config.h>
#include "odp-execute.h"
-#include <arpa/inet.h>
+#include <sys/types.h>
#include <netinet/in.h>
+#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <stdlib.h>
#include <string.h>
+#include "coverage.h"
#include "dp-packet.h"
#include "dpif.h"
#include "netlink.h"
#include "unaligned.h"
#include "util.h"
#include "csum.h"
+#include "conntrack.h"
+#include "openvswitch/vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(odp_execute);
+COVERAGE_DEFINE(datapath_drop_sample_error);
+COVERAGE_DEFINE(datapath_drop_nsh_decap_error);
+COVERAGE_DEFINE(drop_action_of_pipeline);
+COVERAGE_DEFINE(drop_action_bridge_not_found);
+COVERAGE_DEFINE(drop_action_recursion_too_deep);
+COVERAGE_DEFINE(drop_action_too_many_resubmit);
+COVERAGE_DEFINE(drop_action_stack_too_deep);
+COVERAGE_DEFINE(drop_action_no_recirculation_context);
+COVERAGE_DEFINE(drop_action_recirculation_conflict);
+COVERAGE_DEFINE(drop_action_too_many_mpls_labels);
+COVERAGE_DEFINE(drop_action_invalid_tunnel_metadata);
+COVERAGE_DEFINE(drop_action_unsupported_packet_type);
+COVERAGE_DEFINE(drop_action_congestion);
+COVERAGE_DEFINE(drop_action_forwarding_disabled);
+
+static void
+dp_update_drop_action_counter(enum xlate_error drop_reason,
+ int delta)
+{
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ switch (drop_reason) {
+ case XLATE_OK:
+ COVERAGE_ADD(drop_action_of_pipeline, delta);
+ break;
+ case XLATE_BRIDGE_NOT_FOUND:
+ COVERAGE_ADD(drop_action_bridge_not_found, delta);
+ break;
+ case XLATE_RECURSION_TOO_DEEP:
+ COVERAGE_ADD(drop_action_recursion_too_deep, delta);
+ break;
+ case XLATE_TOO_MANY_RESUBMITS:
+ COVERAGE_ADD(drop_action_too_many_resubmit, delta);
+ break;
+ case XLATE_STACK_TOO_DEEP:
+ COVERAGE_ADD(drop_action_stack_too_deep, delta);
+ break;
+ case XLATE_NO_RECIRCULATION_CONTEXT:
+ COVERAGE_ADD(drop_action_no_recirculation_context, delta);
+ break;
+ case XLATE_RECIRCULATION_CONFLICT:
+ COVERAGE_ADD(drop_action_recirculation_conflict, delta);
+ break;
+ case XLATE_TOO_MANY_MPLS_LABELS:
+ COVERAGE_ADD(drop_action_too_many_mpls_labels, delta);
+ break;
+ case XLATE_INVALID_TUNNEL_METADATA:
+ COVERAGE_ADD(drop_action_invalid_tunnel_metadata, delta);
+ break;
+ case XLATE_UNSUPPORTED_PACKET_TYPE:
+ COVERAGE_ADD(drop_action_unsupported_packet_type, delta);
+ break;
+ case XLATE_CONGESTION_DROP:
+ COVERAGE_ADD(drop_action_congestion, delta);
+ break;
+ case XLATE_FORWARDING_DISABLED:
+ COVERAGE_ADD(drop_action_forwarding_disabled, delta);
+ break;
+ case XLATE_MAX:
+ default:
+ VLOG_ERR_RL(&rl, "Invalid Drop reason type: %d", drop_reason);
+ }
+}
/* Masked copy of an ethernet address. 'src' is already properly masked. */
static void
static void
odp_set_tunnel_action(const struct nlattr *a, struct flow_tnl *tun_key)
{
- enum odp_key_fitness fitness;
-
- fitness = odp_tun_key_from_attr(a, tun_key);
- ovs_assert(fitness != ODP_FIT_ERROR);
+ ovs_assert(odp_tun_key_from_attr(a, tun_key, NULL) != ODP_FIT_ERROR);
}
static void
}
}
+static void
+odp_set_nd_ext(struct dp_packet *packet, const struct ovs_key_nd_extensions
+ *key, const struct ovs_key_nd_extensions *mask)
+{
+ const struct ovs_nd_msg *ns = dp_packet_l4(packet);
+ ovs_16aligned_be32 reserved = ns->rso_flags;
+ uint8_t opt_type = ns->options[0].type;
+
+ if (mask->nd_reserved) {
+ put_16aligned_be32(&reserved, key->nd_reserved);
+ }
+ if (mask->nd_options_type) {
+ opt_type = key->nd_options_type;
+ }
+ packet_set_nd_ext(packet, reserved, opt_type);
+}
+
static void
odp_set_nd(struct dp_packet *packet, const struct ovs_key_nd *key,
const struct ovs_key_nd *mask)
}
}
+/* Set the NSH header. Assumes the NSH header is present and matches the
+ * MD format of the key. The slow path must take case of that. */
+static void
+odp_set_nsh(struct dp_packet *packet, const struct nlattr *a, bool has_mask)
+{
+ struct ovs_key_nsh key, mask;
+ struct nsh_hdr *nsh = dp_packet_l3(packet);
+ uint8_t mdtype = nsh_md_type(nsh);
+ ovs_be32 path_hdr;
+
+ if (has_mask) {
+ odp_nsh_key_from_attr(a, &key, &mask, NULL);
+ } else {
+ odp_nsh_key_from_attr(a, &key, NULL, NULL);
+ }
+
+ if (!has_mask) {
+ nsh_set_flags_and_ttl(nsh, key.flags, key.ttl);
+ put_16aligned_be32(&nsh->path_hdr, key.path_hdr);
+ switch (mdtype) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ put_16aligned_be32(&nsh->md1.context[i], key.context[i]);
+ }
+ break;
+ case NSH_M_TYPE2:
+ default:
+ /* No support for setting any other metadata format yet. */
+ break;
+ }
+ } else {
+ uint8_t flags = nsh_get_flags(nsh);
+ uint8_t ttl = nsh_get_ttl(nsh);
+
+ flags = key.flags | (flags & ~mask.flags);
+ ttl = key.ttl | (ttl & ~mask.ttl);
+ nsh_set_flags_and_ttl(nsh, flags, ttl);
+
+ uint32_t spi = ntohl(nsh_get_spi(nsh));
+ uint8_t si = nsh_get_si(nsh);
+ uint32_t spi_mask = nsh_path_hdr_to_spi_uint32(mask.path_hdr);
+ uint8_t si_mask = nsh_path_hdr_to_si(mask.path_hdr);
+ if (spi_mask == 0x00ffffff) {
+ spi_mask = UINT32_MAX;
+ }
+ spi = nsh_path_hdr_to_spi_uint32(key.path_hdr) | (spi & ~spi_mask);
+ si = nsh_path_hdr_to_si(key.path_hdr) | (si & ~si_mask);
+ path_hdr = nsh_get_path_hdr(nsh);
+ nsh_path_hdr_set_spi(&path_hdr, htonl(spi));
+ nsh_path_hdr_set_si(&path_hdr, si);
+ put_16aligned_be32(&nsh->path_hdr, path_hdr);
+ switch (mdtype) {
+ case NSH_M_TYPE1:
+ for (int i = 0; i < 4; i++) {
+ ovs_be32 p = get_16aligned_be32(&nsh->md1.context[i]);
+ ovs_be32 k = key.context[i];
+ ovs_be32 m = mask.context[i];
+ put_16aligned_be32(&nsh->md1.context[i], k | (p & ~m));
+ }
+ break;
+ case NSH_M_TYPE2:
+ default:
+ /* No support for setting any other metadata format yet. */
+ break;
+ }
+ }
+}
+
static void
odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
{
odp_eth_set_addrs(packet, nl_attr_get(a), NULL);
break;
+ case OVS_KEY_ATTR_NSH: {
+ odp_set_nsh(packet, a, false);
+ break;
+ }
+
case OVS_KEY_ATTR_IPV4:
ipv4_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv4));
packet_set_ipv4(packet, ipv4_key->ipv4_src,
}
break;
+ case OVS_KEY_ATTR_ND_EXTENSIONS:
+ if (OVS_LIKELY(dp_packet_get_nd_payload(packet))) {
+ const struct ovs_key_nd_extensions *nd_ext_key
+ = nl_attr_get_unspec(a, sizeof(struct ovs_key_nd_extensions));
+ ovs_16aligned_be32 rso_flags;
+ put_16aligned_be32(&rso_flags, nd_ext_key->nd_reserved);
+ packet_set_nd_ext(packet, rso_flags, nd_ext_key->nd_options_type);
+ }
+ break;
+
case OVS_KEY_ATTR_DP_HASH:
md->dp_hash = nl_attr_get_u32(a);
break;
break;
case OVS_KEY_ATTR_UNSPEC:
+ case OVS_KEY_ATTR_PACKET_TYPE:
case OVS_KEY_ATTR_ENCAP:
case OVS_KEY_ATTR_ETHERTYPE:
case OVS_KEY_ATTR_IN_PORT:
get_mask(a, struct ovs_key_ethernet));
break;
+ case OVS_KEY_ATTR_NSH: {
+ odp_set_nsh(packet, a, true);
+ break;
+ }
+
case OVS_KEY_ATTR_IPV4:
odp_set_ipv4(packet, nl_attr_get(a),
get_mask(a, struct ovs_key_ipv4));
get_mask(a, struct ovs_key_nd));
break;
+ case OVS_KEY_ATTR_ND_EXTENSIONS:
+ odp_set_nd_ext(packet, nl_attr_get(a),
+ get_mask(a, struct ovs_key_nd_extensions));
+ break;
+
case OVS_KEY_ATTR_DP_HASH:
md->dp_hash = nl_attr_get_u32(a)
| (md->dp_hash & ~*get_mask(a, uint32_t));
break;
case OVS_KEY_ATTR_TUNNEL: /* Masked data not supported for tunnel. */
+ case OVS_KEY_ATTR_PACKET_TYPE:
case OVS_KEY_ATTR_UNSPEC:
case OVS_KEY_ATTR_CT_STATE:
case OVS_KEY_ATTR_CT_ZONE:
case OVS_SAMPLE_ATTR_PROBABILITY:
if (random_uint32() >= nl_attr_get_u32(a)) {
if (steal) {
+ COVERAGE_INC(datapath_drop_sample_error);
dp_packet_delete(packet);
}
return;
}
}
+static void
+odp_execute_check_pkt_len(void *dp, struct dp_packet *packet, bool steal,
+ const struct nlattr *action,
+ odp_execute_cb dp_execute_action)
+{
+ static const struct nl_policy ovs_cpl_policy[] = {
+ [OVS_CHECK_PKT_LEN_ATTR_PKT_LEN] = { .type = NL_A_U16 },
+ [OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER] = { .type = NL_A_NESTED },
+ [OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL]
+ = { .type = NL_A_NESTED },
+ };
+ struct nlattr *attrs[ARRAY_SIZE(ovs_cpl_policy)];
+
+ if (!nl_parse_nested(action, ovs_cpl_policy, attrs, ARRAY_SIZE(attrs))) {
+ OVS_NOT_REACHED();
+ }
+
+ const struct nlattr *a;
+ struct dp_packet_batch pb;
+ uint32_t size = dp_packet_get_send_len(packet)
+ - dp_packet_l2_pad_size(packet);
+
+ a = attrs[OVS_CHECK_PKT_LEN_ATTR_PKT_LEN];
+ if (size > nl_attr_get_u16(a)) {
+ a = attrs[OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_GREATER];
+ } else {
+ a = attrs[OVS_CHECK_PKT_LEN_ATTR_ACTIONS_IF_LESS_EQUAL];
+ }
+
+ if (!steal) {
+ /* The 'subactions' may modify the packet, but the modification
+ * should not propagate beyond this action. Make a copy
+ * the packet in case we don't own the packet, so that the
+ * 'subactions' are only applid to check_pkt_len. 'odp_execute_actions'
+ * will free the clone. */
+ packet = dp_packet_clone(packet);
+ }
+ /* If nl_attr_get(a) is NULL, the packet will be freed by
+ * odp_execute_actions. */
+ dp_packet_batch_init_packet(&pb, packet);
+ odp_execute_actions(dp, &pb, true, nl_attr_get(a), nl_attr_get_size(a),
+ dp_execute_action);
+}
+
static bool
requires_datapath_assistance(const struct nlattr *a)
{
switch (type) {
/* These only make sense in the context of a datapath. */
case OVS_ACTION_ATTR_OUTPUT:
+ case OVS_ACTION_ATTR_LB_OUTPUT:
case OVS_ACTION_ATTR_TUNNEL_PUSH:
case OVS_ACTION_ATTR_TUNNEL_POP:
case OVS_ACTION_ATTR_USERSPACE:
case OVS_ACTION_ATTR_PUSH_ETH:
case OVS_ACTION_ATTR_POP_ETH:
case OVS_ACTION_ATTR_CLONE:
+ case OVS_ACTION_ATTR_PUSH_NSH:
+ case OVS_ACTION_ATTR_POP_NSH:
+ case OVS_ACTION_ATTR_CT_CLEAR:
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ case OVS_ACTION_ATTR_DROP:
return false;
case OVS_ACTION_ATTR_UNSPEC:
return false;
}
+/* Executes all of the 'actions_len' bytes of datapath actions in 'actions' on
+ * the packets in 'batch'. If 'steal' is true, possibly modifies and
+ * definitely free the packets in 'batch', otherwise leaves 'batch' unchanged.
+ *
+ * Some actions (e.g. output actions) can only be executed by a datapath. This
+ * function implements those actions by passing the action and the packets to
+ * 'dp_execute_action' (along with 'dp'). If 'dp_execute_action' is passed a
+ * true 'steal' parameter then it must definitely free the packets passed into
+ * it. The packet can be modified whether 'steal' is false or true. If a
+ * packet is removed from the batch, then the fate of the packet is determined
+ * by the code that does this removal, irrespective of the value of 'steal'.
+ * Otherwise, if the packet is not removed from the batch and 'steal' is false
+ * then the packet could either be cloned or not. */
void
odp_execute_actions(void *dp, struct dp_packet_batch *batch, bool steal,
const struct nlattr *actions, size_t actions_len,
if (dp_execute_action) {
/* Allow 'dp_execute_action' to steal the packet data if we do
* not need it any more. */
- bool may_steal = steal && last_action;
+ bool should_steal = steal && last_action;
- dp_execute_action(dp, batch, a, may_steal);
+ dp_execute_action(dp, batch, a, should_steal);
- if (last_action) {
- /* We do not need to free the packets. dp_execute_actions()
- * has stolen them */
+ if (last_action || dp_packet_batch_is_empty(batch)) {
+ /* We do not need to free the packets.
+ * Either dp_execute_actions() has stolen them
+ * or the batch is freed due to errors. In either
+ * case we do not need to execute further actions.
+ */
return;
}
}
}
switch ((enum ovs_action_attr) type) {
+
case OVS_ACTION_ATTR_HASH: {
const struct ovs_action_hash *hash_act = nl_attr_get(a);
- /* Calculate a hash value directly. This might not match the
+ /* Calculate a hash value directly. This might not match the
* value computed by the datapath, but it is much less expensive,
* and the current use case (bonding) does not require a strict
* match to work properly. */
- if (hash_act->hash_alg == OVS_HASH_ALG_L4) {
+ switch (hash_act->hash_alg) {
+ case OVS_HASH_ALG_L4: {
struct flow flow;
uint32_t hash;
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ /* RSS hash can be used here instead of 5tuple for
+ * performance reasons. */
+ if (dp_packet_rss_valid(packet)) {
+ hash = dp_packet_get_rss_hash(packet);
+ hash = hash_int(hash, hash_act->hash_basis);
+ } else {
+ flow_extract(packet, &flow);
+ hash = flow_hash_5tuple(&flow, hash_act->hash_basis);
+ }
+ packet->md.dp_hash = hash;
+ }
+ break;
+ }
+ case OVS_HASH_ALG_SYM_L4: {
+ struct flow flow;
+ uint32_t hash;
+
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
flow_extract(packet, &flow);
- hash = flow_hash_5tuple(&flow, hash_act->hash_basis);
+ hash = flow_hash_symmetric_l3l4(&flow,
+ hash_act->hash_basis,
+ false);
packet->md.dp_hash = hash;
}
- } else {
+ break;
+ }
+ default:
/* Assert on unknown hash algorithm. */
OVS_NOT_REACHED();
}
case OVS_ACTION_ATTR_PUSH_VLAN: {
const struct ovs_action_push_vlan *vlan = nl_attr_get(a);
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
eth_push_vlan(packet, vlan->vlan_tpid, vlan->vlan_tci);
}
break;
}
case OVS_ACTION_ATTR_POP_VLAN:
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
eth_pop_vlan(packet);
}
break;
case OVS_ACTION_ATTR_PUSH_MPLS: {
const struct ovs_action_push_mpls *mpls = nl_attr_get(a);
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
push_mpls(packet, mpls->mpls_ethertype, mpls->mpls_lse);
}
break;
}
case OVS_ACTION_ATTR_POP_MPLS:
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
pop_mpls(packet, nl_attr_get_be16(a));
}
break;
case OVS_ACTION_ATTR_SET:
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
odp_execute_set_action(packet, nl_attr_get(a));
}
break;
case OVS_ACTION_ATTR_SET_MASKED:
- DP_PACKET_BATCH_FOR_EACH(packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH(i, packet, batch) {
odp_execute_masked_set_action(packet, nl_attr_get(a));
}
break;
case OVS_ACTION_ATTR_SAMPLE:
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
odp_execute_sample(dp, packet, steal && last_action, a,
dp_execute_action);
}
nl_attr_get_unspec(a, sizeof *trunc);
batch->trunc = true;
- DP_PACKET_BATCH_FOR_EACH (packet, batch) {
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
dp_packet_set_cutlen(packet, trunc->max_len);
}
break;
* stolen them. */
return;
}
+ break;
case OVS_ACTION_ATTR_METER:
/* Not implemented yet. */
break;
+ case OVS_ACTION_ATTR_PUSH_ETH: {
+ const struct ovs_action_push_eth *eth = nl_attr_get(a);
+
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ push_eth(packet, ð->addresses.eth_dst,
+ ð->addresses.eth_src);
+ }
+ break;
+ }
+
+ case OVS_ACTION_ATTR_POP_ETH:
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ pop_eth(packet);
+ }
+ break;
+
+ case OVS_ACTION_ATTR_PUSH_NSH: {
+ uint32_t buffer[NSH_HDR_MAX_LEN / 4];
+ struct nsh_hdr *nsh_hdr = ALIGNED_CAST(struct nsh_hdr *, buffer);
+ nsh_reset_ver_flags_ttl_len(nsh_hdr);
+ odp_nsh_hdr_from_attr(nl_attr_get(a), nsh_hdr, NSH_HDR_MAX_LEN);
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ push_nsh(packet, nsh_hdr);
+ }
+ break;
+ }
+ case OVS_ACTION_ATTR_POP_NSH: {
+ size_t i;
+ const size_t num = dp_packet_batch_size(batch);
+
+ DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) {
+ if (pop_nsh(packet)) {
+ dp_packet_batch_refill(batch, packet, i);
+ } else {
+ COVERAGE_INC(datapath_drop_nsh_decap_error);
+ dp_packet_delete(packet);
+ }
+ }
+ break;
+ }
+ case OVS_ACTION_ATTR_CT_CLEAR:
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ conntrack_clear(packet);
+ }
+ break;
+
+ case OVS_ACTION_ATTR_CHECK_PKT_LEN:
+ DP_PACKET_BATCH_FOR_EACH (i, packet, batch) {
+ odp_execute_check_pkt_len(dp, packet, steal && last_action, a,
+ dp_execute_action);
+ }
+ if (last_action) {
+ /* We do not need to free the packets.
+ * odp_execute_check_pkt_len() has stolen them. */
+ return;
+ }
+ break;
+
+ case OVS_ACTION_ATTR_DROP:{
+ const enum xlate_error *drop_reason = nl_attr_get(a);
+
+ dp_update_drop_action_counter(*drop_reason,
+ dp_packet_batch_size(batch));
+ dp_packet_delete_batch(batch, steal);
+ return;
+ }
case OVS_ACTION_ATTR_OUTPUT:
+ case OVS_ACTION_ATTR_LB_OUTPUT:
case OVS_ACTION_ATTR_TUNNEL_PUSH:
case OVS_ACTION_ATTR_TUNNEL_POP:
case OVS_ACTION_ATTR_USERSPACE:
case OVS_ACTION_ATTR_RECIRC:
case OVS_ACTION_ATTR_CT:
- case OVS_ACTION_ATTR_PUSH_ETH:
- case OVS_ACTION_ATTR_POP_ETH:
case OVS_ACTION_ATTR_UNSPEC:
case __OVS_ACTION_ATTR_MAX:
OVS_NOT_REACHED();