Eelco Chaudron via dev <[email protected]> writes:
> This patch extends the dummy offload provider to simulate full
> hardware offload behavior, where packets matching offloaded
> flows can be processed entirely in "hardware" without falling
> back to the software datapath.
>
> Previously, the dummy offload provider only simulated partial
> offload, where flows were marked as offloaded but all packet
> processing still occurred in software.
>
> Signed-off-by: Eelco Chaudron <[email protected]>
> ---
Hi Eelco,
Thanks for the patch - I think I can also see how to integrate the
ct-offload series with this.
> lib/dpif-offload-dummy.c | 294 +++++++++++++++++++++++++++++++++++----
> lib/dummy.h | 8 +-
> lib/netdev-dummy.c | 56 ++++++--
> tests/dpif-netdev.at | 124 +++++++++++++++++
> tests/ofproto-dpif.at | 90 ++++++++++--
> 5 files changed, 517 insertions(+), 55 deletions(-)
>
> diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c
> index b7b24d6064..28f1f40013 100644
> --- a/lib/dpif-offload-dummy.c
> +++ b/lib/dpif-offload-dummy.c
> @@ -42,8 +42,11 @@ struct pmd_id_data {
> struct dummy_offloaded_flow {
> struct hmap_node node;
> struct match match;
> + const struct nlattr *actions;
> + size_t actions_len;
> ovs_u128 ufid;
> uint32_t mark;
> + struct dpif_flow_stats stats;
>
> /* The pmd_id_map below is also protected by the port_mutex. */
> struct hmap pmd_id_map;
> @@ -63,6 +66,19 @@ struct dummy_offload_port {
>
> struct ovs_mutex port_mutex; /* Protect all below members. */
> struct hmap offloaded_flows OVS_GUARDED;
> + struct ovs_list hw_recv_queue OVS_GUARDED;
> +
> + /* Some simulated offload statistics. */
> + uint64_t rx_offload_partial OVS_GUARDED; /* Match found, CPU continues.
> */
> + uint64_t rx_offload_full OVS_GUARDED; /* Fully offloaded, CPU bypassed.
> */
> + uint64_t rx_offload_miss OVS_GUARDED; /* No HW offload rule matched. */
> + uint64_t rx_offload_pipe_abort OVS_GUARDED; /* Pipeline abort. */
> +};
> +
> +struct hw_pkt_node {
> + struct dp_packet *pkt;
> + int queue_id;
> + struct ovs_list list_node;
> };
>
> static void dummy_flow_unreference(struct dummy_offload *, unsigned pmd_id,
> @@ -228,6 +244,7 @@ dummy_free_flow(struct dummy_offload_port *port,
> ovs_assert(!hmap_count(&off_flow->pmd_id_map));
>
> hmap_destroy(&off_flow->pmd_id_map);
> + free(CONST_CAST(struct nlattr *, off_flow->actions));
> free(off_flow);
> }
>
> @@ -288,6 +305,7 @@ dummy_free_port__(struct dummy_offload *offload,
> struct dummy_offload_port *port, bool close_netdev)
> {
> struct dummy_offloaded_flow *off_flow;
> + struct hw_pkt_node *pkt;
>
> ovs_mutex_lock(&port->port_mutex);
> HMAP_FOR_EACH_POP (off_flow, node, &port->offloaded_flows) {
> @@ -295,6 +313,12 @@ dummy_free_port__(struct dummy_offload *offload,
> dummy_free_flow(port, off_flow, false);
> }
> hmap_destroy(&port->offloaded_flows);
> +
> + LIST_FOR_EACH_POP (pkt, list_node, &port->hw_recv_queue) {
> + dp_packet_delete(pkt->pkt);
> + free(pkt);
> + }
> +
> ovs_mutex_unlock(&port->port_mutex);
> ovs_mutex_destroy(&port->port_mutex);
> if (close_netdev) {
> @@ -330,11 +354,12 @@ dummy_offload_port_add(struct dpif_offload
> *dpif_offload,
> struct netdev *netdev, odp_port_t port_no)
> {
> struct dummy_offload *offload = dummy_offload_cast(dpif_offload);
> - struct dummy_offload_port *port = xmalloc(sizeof *port);
> + struct dummy_offload_port *port = xzalloc(sizeof *port);
>
> ovs_mutex_init(&port->port_mutex);
> ovs_mutex_lock(&port->port_mutex);
> hmap_init(&port->offloaded_flows);
> + ovs_list_init(&port->hw_recv_queue);
> ovs_mutex_unlock(&port->port_mutex);
>
> if (dpif_offload_port_mgr_add(dpif_offload, &port->pm_port, netdev,
> @@ -445,15 +470,27 @@ dummy_offload_get_debug(const struct dpif_offload
> *offload, struct ds *ds,
> {
> if (json) {
> struct json *json_ports = json_object_create();
> - struct dpif_offload_port *port;
> + struct dpif_offload_port *port_;
>
> - DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) {
> + DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> + struct dummy_offload_port *port = dummy_offload_port_cast(port_);
> struct json *json_port = json_object_create();
>
> json_object_put(json_port, "port_no",
> - json_integer_create(odp_to_u32(port->port_no)));
> -
> - json_object_put(json_ports, netdev_get_name(port->netdev),
> + json_integer_create(odp_to_u32(port_->port_no)));
> +
> + ovs_mutex_lock(&port->port_mutex);
> + json_object_put(json_port, "rx_offload_partial",
> + json_integer_create(port->rx_offload_partial));
> + json_object_put(json_port, "rx_offload_full",
> + json_integer_create(port->rx_offload_full));
> + json_object_put(json_port, "rx_offload_miss",
> + json_integer_create(port->rx_offload_miss));
> + json_object_put(json_port, "rx_offload_pipe_abort",
> +
> json_integer_create(port->rx_offload_pipe_abort));
> + ovs_mutex_unlock(&port->port_mutex);
> +
> + json_object_put(json_ports, netdev_get_name(port_->netdev),
> json_port);
> }
>
> @@ -463,11 +500,22 @@ dummy_offload_get_debug(const struct dpif_offload
> *offload, struct ds *ds,
> json_destroy(json_ports);
> }
> } else if (ds) {
> - struct dpif_offload_port *port;
> -
> - DPIF_OFFLOAD_PORT_FOR_EACH (port, offload) {
> - ds_put_format(ds, " - %s: port_no: %u\n",
> - netdev_get_name(port->netdev), port->port_no);
> + struct dpif_offload_port *port_;
> +
> + DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> + struct dummy_offload_port *port = dummy_offload_port_cast(port_);
> +
> + ovs_mutex_lock(&port->port_mutex);
> + ds_put_format(ds,
> + " - %s: port_no: %u\n"
> + " rx_offload_partial : %" PRIu64 "\n"
> + " rx_offload_full : %" PRIu64 "\n"
> + " rx_offload_miss : %" PRIu64 "\n"
> + " rx_offload_pipe_abort: %" PRIu64 "\n",
> + netdev_get_name(port_->netdev), port_->port_no,
> + port->rx_offload_partial, port->rx_offload_full,
> + port->rx_offload_miss,
> port->rx_offload_pipe_abort);
> + ovs_mutex_unlock(&port->port_mutex);
> }
> }
> }
> @@ -515,6 +563,19 @@ dummy_offload_get_port_by_netdev(const struct
> dpif_offload *offload,
> return dummy_offload_port_cast(port);
> }
>
> +static struct dummy_offload_port *
> +dummy_offload_get_port_by_odp_port(const struct dpif_offload *offload_,
> + odp_port_t port_no)
> +{
> + struct dpif_offload_port *port;
> +
> + port = dpif_offload_port_mgr_find_by_odp_port(offload_, port_no);
> + if (!port) {
> + return NULL;
> + }
> + return dummy_offload_port_cast(port);
> +}
> +
> static int
> dummy_offload_hw_post_process(const struct dpif_offload *offload_,
> struct netdev *netdev, unsigned pmd_id,
> @@ -549,6 +610,86 @@ dummy_offload_hw_post_process(const struct dpif_offload
> *offload_,
> return 0;
> }
>
> +static bool
> +dummy_offload_are_all_actions_supported(const struct dpif_offload *offload_,
> + odp_port_t in_odp,
> + const struct nlattr *actions,
> + size_t actions_len)
> +{
> + const struct nlattr *nla;
> + size_t left;
> +
> + /* Can we fully offload this flow? For now, only output actions are
> + * supported, and only to dummy-pmd netdevs where the egress port differs
> + * from the ingress port. The latter restriction ensures that the
> partial
> + * offload test cases pass.
> + *
> + * The reason for supporting only dummy-pmd netdevs as output targets is
> + * that they provide full protection when calling netdev_send() from any
> + * thread, via a netdev-level mutex. */
> + NL_ATTR_FOR_EACH (nla, left, actions, actions_len) {
> + if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> + odp_port_t out_odp = nl_attr_get_odp_port(nla);
> + struct dummy_offload_port *out_port;
> +
> + out_port = dummy_offload_get_port_by_odp_port(offload_, out_odp);
> + if (out_odp == in_odp || !out_port
> + || strcmp("dummy-pmd",
> + netdev_get_type(out_port->pm_port.netdev))) {
> + return false;
> + }
> + } else {
> + return false;
> + }
> + }
> + return true;
> +}
> +
> +static bool
> +dummy_offload_hw_process_pkt(const struct dpif_offload *offload_,
> + struct dummy_offloaded_flow *flow,
> + struct dp_packet *pkt)
> +{
> + uint32_t hash = dp_packet_get_rss_hash(pkt);
> + uint32_t pkt_size = dp_packet_size(pkt);
> + const struct nlattr *nla;
> + size_t left;
> +
> + if (!flow->actions) {
> + return false;
> + }
> +
> + NL_ATTR_FOR_EACH (nla, left, flow->actions, flow->actions_len) {
> + bool last_action = (left <= NLA_ALIGN(nla->nla_len));
> +
> + if (nl_attr_type(nla) == OVS_ACTION_ATTR_OUTPUT) {
> + odp_port_t odp_port = nl_attr_get_odp_port(nla);
> + struct dummy_offload_port *port;
> + struct dp_packet_batch batch;
> + int n_txq;
> +
> + port = dummy_offload_get_port_by_odp_port(offload_, odp_port);
> + if (!port) {
> + return false;
> + }
> +
> + n_txq = netdev_n_txq(port->pm_port.netdev);
> + dp_packet_batch_init_packet(&batch, last_action
> + ? pkt
> + : dp_packet_clone(pkt));
> + /* As the tx-steering option is not exposed to hardware offload,
> + * for now we assume hash steering based on the number of queues
> + * configured for the dummy-netdev. */
> + netdev_send(port->pm_port.netdev, hash % n_txq, &batch, false);
> + }
> + }
> +
> + flow->stats.n_bytes += pkt_size;
> + flow->stats.n_packets++;
> + flow->stats.used = time_msec();
Maybe it doesn't matter too much in this case, but these stats are being
updated even if netdev_send() fails above. Also, I guess technically
there could be some kind of race with the flow_put since that does an
assignment (but I might have missed a lock).
> + return true;
> +}
> +
> static int
> dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev,
> struct dpif_offload_flow_put *put,
> @@ -558,6 +699,7 @@ dummy_flow_put(const struct dpif_offload *offload_,
> struct netdev *netdev,
> struct dummy_offloaded_flow *off_flow;
> struct dummy_offload_port *port;
> bool modify = true;
> + bool full_offload;
> int error = 0;
>
> port = dummy_offload_get_port_by_netdev(offload_, netdev);
> @@ -566,6 +708,10 @@ dummy_flow_put(const struct dpif_offload *offload_,
> struct netdev *netdev,
> goto exit;
> }
>
> + full_offload = dummy_offload_are_all_actions_supported(
> + offload_, put->match->flow.in_port.odp_port,
> + put->actions, put->actions_len);
> +
> ovs_mutex_lock(&port->port_mutex);
>
> off_flow = dummy_find_offloaded_flow_and_update(
> @@ -587,6 +733,14 @@ dummy_flow_put(const struct dpif_offload *offload_,
> struct netdev *netdev,
> *previous_flow_reference = NULL;
> }
> memcpy(&off_flow->match, put->match, sizeof *put->match);
> + free(CONST_CAST(struct nlattr *, off_flow->actions));
> + if (full_offload) {
> + off_flow->actions = xmemdup(put->actions, put->actions_len);
> + off_flow->actions_len = put->actions_len;
> + } else {
> + off_flow->actions = NULL;
> + off_flow->actions_len = 0;
> + }
>
> /* As we have per-netdev 'offloaded_flows', we don't need to match
> * the 'in_port' for received packets. This will also allow offloading
> @@ -609,13 +763,13 @@ dummy_flow_put(const struct dpif_offload *offload_,
> struct netdev *netdev,
> }
>
> exit_unlock:
> - ovs_mutex_unlock(&port->port_mutex);
> -
> -exit:
> if (put->stats) {
> - memset(put->stats, 0, sizeof *put->stats);
> + *put->stats = off_flow->stats;
> }
>
> + ovs_mutex_unlock(&port->port_mutex);
> +
> +exit:
> dummy_offload_log_operation(modify ? "modify" : "add", error, put->ufid);
> return error;
> }
> @@ -650,6 +804,10 @@ dummy_flow_del(const struct dpif_offload *offload_,
> struct netdev *netdev,
> goto exit_unlock;
> }
>
> + if (del->stats) {
> + memcpy(del->stats, &off_flow->stats, sizeof *del->stats);
> + }
> +
> mark = off_flow->mark;
> if (!hmap_count(&off_flow->pmd_id_map)) {
> dummy_free_flow_mark(offload, mark);
> @@ -678,10 +836,6 @@ exit:
> ds_destroy(&ds);
> }
>
> - if (del->stats) {
> - memset(del->stats, 0, sizeof *del->stats);
> - }
> -
> dummy_offload_log_operation("delete", error ? -1 : 0, del->ufid);
> return error ? ENOENT : 0;
> }
> @@ -701,14 +855,19 @@ dummy_flow_stats(const struct dpif_offload *offload_,
> struct netdev *netdev,
>
> ovs_mutex_lock(&port->port_mutex);
> off_flow = dummy_find_offloaded_flow(port, ufid);
> + if (off_flow) {
> + memcpy(stats, &off_flow->stats, sizeof *stats);
> + attrs->dp_layer = off_flow->actions ? "dummy" : "ovs";
> + attrs->dp_extra_info = NULL;
> + attrs->offloaded = true;
> + }
> ovs_mutex_unlock(&port->port_mutex);
>
> - memset(stats, 0, sizeof *stats);
> - attrs->offloaded = off_flow ? true : false;
> - attrs->dp_layer = "ovs"; /* 'ovs', since this is a partial offload. */
> - attrs->dp_extra_info = NULL;
> + if (!off_flow) {
> + return false;
> + }
>
> - return off_flow ? true : false;
> + return true;
> }
>
> static void
> @@ -729,23 +888,26 @@ dummy_flow_unreference(struct dummy_offload *offload,
> unsigned pmd_id,
> }
> }
>
> -void
> +bool
> dummy_netdev_simulate_offload(struct netdev *netdev, struct dp_packet
> *packet,
> - struct flow *flow)
> + int queue_id, struct flow *flow)
> {
> const struct dpif_offload *offload = ovsrcu_get(
> const struct dpif_offload *, &netdev->dpif_offload);
> struct dummy_offloaded_flow *data;
> struct dummy_offload_port *port;
> + bool packet_stolen = false;
> struct flow packet_flow;
> + bool offloaded = false;
>
> - if (!offload || strcmp(dpif_offload_type(offload), "dummy")) {
> - return;
> + if (!dpif_offload_enabled() || !offload
> + || strcmp(dpif_offload_type(offload), "dummy")) {
> + return false;
> }
>
> port = dummy_offload_get_port_by_netdev(offload, netdev);
> if (!port) {
> - return;
> + return false;
> }
>
> if (!flow) {
> @@ -778,10 +940,84 @@ dummy_netdev_simulate_offload(struct netdev *netdev,
> struct dp_packet *packet,
> VLOG_DBG("%s", ds_cstr(&ds));
> ds_destroy(&ds);
> }
> +
> + if (data->actions) {
> + /* Perform hardware offload simulation. The packet is stolen
> + * here and handed off to the PMD thread callback for
> + * processing. */
> + struct hw_pkt_node *pkt_node = xmalloc(sizeof *pkt_node);
> +
> + pkt_node->pkt = packet;
> + pkt_node->queue_id = queue_id;
> + ovs_list_push_back(&port->hw_recv_queue,
> &pkt_node->list_node);
> + packet_stolen = true;
> + port->rx_offload_full++;
> + } else {
> + port->rx_offload_partial++;
> + }
> +
> + offloaded = true;
> break;
> }
> }
> +
> + if (!offloaded) {
> + port->rx_offload_miss++;
> + }
> +
> ovs_mutex_unlock(&port->port_mutex);
> + return packet_stolen;
> +}
> +
> +void
> +dummy_netdev_hw_offload_run(struct netdev *netdev)
> +{
> + const struct dpif_offload *offload = ovsrcu_get(
> + const struct dpif_offload *, &netdev->dpif_offload);
> + struct dpif_offload_port *port_;
> +
> + if (!dpif_offload_enabled() || !offload
> + || strcmp(dpif_offload_type(offload), "dummy")) {
> + return;
> + }
> +
> + DPIF_OFFLOAD_PORT_FOR_EACH (port_, offload) {
> + struct dummy_offload_port *port;
> + struct hw_pkt_node *pkt_node;
> +
> + port = dummy_offload_port_cast(port_);
> +
> + if (ovs_mutex_trylock(&port->port_mutex)) {
> + continue;
> + }
> +
> + LIST_FOR_EACH_POP (pkt_node, list_node, &port->hw_recv_queue) {
> + struct dummy_offloaded_flow *offloaded_flow;
> + struct dp_packet *pkt = pkt_node->pkt;
> + bool processed = false;
> + struct flow flow;
> +
> + flow_extract(pkt, &flow);
> + HMAP_FOR_EACH (offloaded_flow, node, &port->offloaded_flows) {
> + if (flow_equal_except(&flow, &offloaded_flow->match.flow,
> + &offloaded_flow->match.wc)) {
> +
> + processed = dummy_offload_hw_process_pkt(
> + offload, offloaded_flow, pkt);
> + break;
> + }
> + }
> +
> + if (!processed) {
> + VLOG_DBG("Failed HW pipeline, sent to sw!");
> + port->rx_offload_pipe_abort++;
> + netdev_dummy_queue_simulate_offload_packet(
> + port->pm_port.netdev, pkt, pkt_node->queue_id);
> + }
> + free(pkt_node);
> + }
> + ovs_mutex_unlock(&port->port_mutex);
> + }
> }
>
> #define DEFINE_DPIF_DUMMY_CLASS(NAME, TYPE_STR) \
> diff --git a/lib/dummy.h b/lib/dummy.h
> index e5c9d4071c..f250ae5933 100644
> --- a/lib/dummy.h
> +++ b/lib/dummy.h
> @@ -41,10 +41,14 @@ void dummy_enable(const char *arg);
> /* Implementation details. */
> void dpif_dummy_register(enum dummy_level);
> void netdev_dummy_register(enum dummy_level);
> +void netdev_dummy_queue_simulate_offload_packet(const struct netdev *,
> + struct dp_packet *,
> + int queue_id);
> void timeval_dummy_register(void);
> void ofpact_dummy_enable(void);
> bool is_dummy_netdev_class(const struct netdev_class *);
> -void dummy_netdev_simulate_offload(struct netdev *, struct dp_packet *,
> - struct flow *);
> +bool dummy_netdev_simulate_offload(struct netdev *, struct dp_packet *,
> + int queue_id, struct flow *);
> +void dummy_netdev_hw_offload_run(struct netdev *);
>
> #endif /* dummy.h */
> diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c
> index 7d3a7b9682..d5e8599349 100644
> --- a/lib/netdev-dummy.c
> +++ b/lib/netdev-dummy.c
> @@ -695,6 +695,7 @@ netdev_dummy_run(const struct netdev_class *netdev_class)
> ovs_mutex_lock(&dev->mutex);
> dummy_packet_conn_run(dev);
> ovs_mutex_unlock(&dev->mutex);
> + dummy_netdev_hw_offload_run(&dev->up);
> }
> ovs_mutex_unlock(&dummy_list_mutex);
> }
> @@ -1875,7 +1876,7 @@ eth_from_flow_str(const char *s, size_t packet_size,
> }
>
> static void
> -netdev_dummy_queue_packet__(struct netdev_rxq_dummy *rx, struct dp_packet
> *packet)
> +netdev_dummy_rxq_enqueue(struct netdev_rxq_dummy *rx, struct dp_packet
> *packet)
> {
> struct pkt_list_node *pkt_node = xmalloc(sizeof *pkt_node);
>
> @@ -1886,35 +1887,64 @@ netdev_dummy_queue_packet__(struct netdev_rxq_dummy
> *rx, struct dp_packet *packe
> }
>
> static void
> -netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct dp_packet
> *packet,
> - struct flow *flow, int queue_id)
> +netdev_dummy_queue_packet__(struct netdev_dummy *dummy,
> + struct dp_packet *packet, int queue_id)
> OVS_REQUIRES(dummy->mutex)
> {
> - struct netdev_rxq_dummy *rx, *prev;
> -
> - if (dummy->rxq_pcap) {
> - ovs_pcap_write(dummy->rxq_pcap, packet);
> - }
> + struct netdev_rxq_dummy *rx, *prev = NULL;
>
> - dummy_netdev_simulate_offload(&dummy->up, packet, flow);
> -
> - prev = NULL;
> LIST_FOR_EACH (rx, node, &dummy->rxes) {
> if (rx->up.queue_id == queue_id &&
> rx->recv_queue_len < NETDEV_DUMMY_MAX_QUEUE) {
> if (prev) {
> - netdev_dummy_queue_packet__(prev, dp_packet_clone(packet));
> + netdev_dummy_rxq_enqueue(prev, dp_packet_clone(packet));
> }
> prev = rx;
> }
> }
> if (prev) {
> - netdev_dummy_queue_packet__(prev, packet);
> + netdev_dummy_rxq_enqueue(prev, packet);
> } else {
> dp_packet_delete(packet);
> }
> }
>
> +static void
> +netdev_dummy_queue_packet(struct netdev_dummy *dummy, struct dp_packet
> *packet,
> + struct flow *flow, int queue_id)
> + OVS_REQUIRES(dummy->mutex)
> +{
> + if (dummy->rxq_pcap) {
> + ovs_pcap_write(dummy->rxq_pcap, packet);
> + }
> +
> + if (dummy_netdev_simulate_offload(&dummy->up, packet,
> + queue_id, flow)) {
> + /* Packet was stolen for full HW offload simulation. */
> + return;
> + }
> +
> + netdev_dummy_queue_packet__(dummy, packet, queue_id);
> +}
> +
> +void netdev_dummy_queue_simulate_offload_packet(const struct netdev *netdev,
> + struct dp_packet *packet,
> + int queue_id)
> +{
> + struct netdev_dummy *dummy;
> +
> + if (!netdev || !is_dummy_netdev_class(netdev->netdev_class)) {
> + dp_packet_delete(packet);
> + return;
> + }
> +
> + dummy = netdev_dummy_cast(netdev);
> +
> + ovs_mutex_lock(&dummy->mutex);
> + netdev_dummy_queue_packet__(dummy, packet, queue_id);
> + ovs_mutex_unlock(&dummy->mutex);
> +}
> +
> static void
> netdev_dummy_receive(struct unixctl_conn *conn,
> int argc, const char *argv[], void *aux OVS_UNUSED)
> diff --git a/tests/dpif-netdev.at b/tests/dpif-netdev.at
> index 2311979709..0480730cab 100644
> --- a/tests/dpif-netdev.at
> +++ b/tests/dpif-netdev.at
> @@ -32,6 +32,20 @@ strip_xout_keep_actions () {
> ' | sort
> }
>
> +strip_hw_offload () {
> + sed '
> + /^flow-dump from/d
> + s/ufid:[-0-9a-f]*,//
> + s/used:[0-9\.][0-9\.]*/used:0.0/
> + s/[^,]*(0\/0),//g
> + s/eth([^)]*)/eth()/
> + s/ipv4([^)]*)/ipv4()/
> + s/udp([^)]*)/udp()/
> + s/, dp-extra-info.*//
> + s/^[[:space:]]*//
> +' | sort
> +}
> +
> filter_flow_install () {
> grep 'flow_add' | sed 's/.*flow_add: //' | sort | uniq
> }
> @@ -637,6 +651,116 @@
> arp,in_port=ANY,dl_vlan=11,dl_vlan_pcp=7,vlan_tci1=0x0000,dl_src=00:06:07:08:09:
> DPIF_NETDEV_FLOW_HW_OFFLOAD_OFFSETS_VID_ARP([dummy])
> DPIF_NETDEV_FLOW_HW_OFFLOAD_OFFSETS_VID_ARP([dummy-pmd])
>
> +AT_SETUP([dpif-netdev - full hw offload - dummy-pmd])
> +OVS_VSWITCHD_START(
> + [add-port br0 p1 -- \
> + add-port br0 p2 -- \
> + set interface p1 type=dummy-pmd ofport_request=1 options:ifindex=1100 -- \
> + set interface p2 type=dummy-pmd ofport_request=2 options:ifindex=1200 \
> + options:tx_pcap=p2.pcap -- \
> + set bridge br0 datapath-type=dummy other-config:datapath-id=1234 \
> + fail-mode=secure], [], [], [--dummy-numa="0,0,0,0,1,1,1,1"])
> +AT_CHECK([ovs-appctl vlog/set dpif:file:dbg dpif_netdev:file:dbg \
> + dpif_offload_dummy:file:dbg])
> +
> +AT_CHECK([ovs-vsctl set Open_vSwitch . other_config:hw-offload=true])
> +OVS_WAIT_UNTIL([grep "Flow HW offload is enabled" ovs-vswitchd.log])
> +
> +AT_CHECK([ovs-ofctl del-flows br0])
> +AT_CHECK([ovs-ofctl add-flow br0 in_port=1,actions=p2])
> +
> +packet="\
> + eth_src=00:06:07:08:09:0a,eth_dst=00:01:02:03:04:05,\
> + udp,ip_src=127.0.0.1,ip_dst=127.0.0.1,nw_ttl=64,\
> + udp_src=54392,udp_dst=5201"
> +packet_hex=$(ovs-ofctl compose-packet --bare "${packet}")
> +
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +
> +OVS_WAIT_UNTIL([grep "miss upcall" ovs-vswitchd.log])
> +AT_CHECK([grep -A 1 'miss upcall' ovs-vswitchd.log | tail -n 1], [0], [dnl
> +recirc_id(0),dp_hash(0),skb_priority(0),in_port(1),skb_mark(0),ct_state(0),ct_zone(0),ct_mark(0),ct_label(0),packet_type(ns=0,id=0),eth(src=00:06:07:08:09:0a,dst=00:01:02:03:04:05),eth_type(0x0800),ipv4(src=127.0.0.1,dst=127.0.0.1,proto=17,tos=0,ttl=64,frag=no),udp(src=54392,dst=5201)
> +])
> +
> +# Check that flow successfully offloaded.
> +OVS_WAIT_UNTIL([grep "succeed to add netdev flow" ovs-vswitchd.log])
> +AT_CHECK([filter_hw_flow_install < ovs-vswitchd.log | strip_xout], [0], [dnl
> +p1: flow put[[create]]: flow match:
> recirc_id=0,eth,ip,in_port=1,vlan_tci=0x0000/0x1fff,nw_frag=no, mark: 1
> +])
> +
> +# Check that datapath flow installed successfully.
> +AT_CHECK([filter_flow_install < ovs-vswitchd.log | strip_xout], [0], [dnl
> +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no),
> actions: <del>
> +])
> +
> +# Inject the same packet again.
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +
> +# Check for succesfull packet matching with installed offloaded flow.
> +AT_CHECK([filter_hw_packet_netdev_dummy < ovs-vswitchd.log | strip_xout],
> [0], [dnl
> +p1: packet:
> udp,vlan_tci=0x0000,dl_src=00:06:07:08:09:0a,dl_dst=00:01:02:03:04:05,nw_src=127.0.0.1,nw_dst=127.0.0.1,nw_tos=0,nw_ecn=0,nw_ttl=64,nw_frag=no,tp_src=54392,tp_dst=5201
> matches with flow: recirc_id=0,eth,ip,vlan_tci=0x0000/0x1fff,nw_frag=no with
> mark: 1
> +])
> +
> +# Dump the datapath flow to see that actions was executed for a packet.
> +AT_CHECK([ovs-appctl dpctl/dump-flows -m | strip_hw_offload], [0], [dnl
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(),eth_type(0x0800),ipv4(),udp(),
> packets:1, bytes:106, used:0.0s, offloaded:yes, dp:dummy, actions:p2
> +])
> +
> +# Wait for datapath flow expiration.
> +ovs-appctl time/stop
> +ovs-appctl time/warp 15000
> +ovs-appctl revalidator/wait
> +
> +# Check that flow successfully deleted from HW.
> +OVS_WAIT_UNTIL([grep "succeed to delete netdev flow" ovs-vswitchd.log])
> +AT_CHECK([filter_hw_flow_del < ovs-vswitchd.log | strip_xout], [0], [dnl
> +p1: flow del: mark: 1
> +])
> +
> +# Check if we do not hit partial hw offload.
> +AT_CHECK([ovs-appctl dpif-netdev/pmd-perf-show \
> + | grep -q "PHWOL hits: 0 "])
> +
> +# Verify two packets where received.
> +AT_CHECK([[[ $(ovs-pcap p2.pcap | grep -c "$packet_hex") -eq 2 ]]])
> +
> +# Verify that we observe one miss, one packet processed by hardware,
> +# and no offload pipeline aborts.
> +AT_CHECK(
> + [ovs-appctl --format json dpif/offload/show \
> + | sed 's/.*"p1":{\([[^}]]*\)}.*/\1/; s/,/\n/g; s/"//g' \
> + | sed -n '/^rx_offload_/p' | sort], [0], [dnl
> +rx_offload_full:1
> +rx_offload_miss:1
> +rx_offload_partial:0
> +rx_offload_pipe_abort:0
> +])
> +
> +# In this test, we remove an output port while the revalidator is disabled.
> +# The hardware flow remains, but the port can no longer be found, causing
> +# the offload simulation to fail egressing. This simulates an offload
> +# pipeline abort, so the packet should fall back to normal processing.
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +AT_CHECK([ovs-appctl revalidator/pause])
> +AT_CHECK([ovs-vsctl del-port br0 p2])
> +AT_CHECK([ovs-appctl netdev-dummy/receive p1 $packet_hex], [0])
> +AT_CHECK([ovs-appctl dpctl/dump-flows -m | strip_hw_offload], [0], [dnl
> +recirc_id(0),in_port(p1),packet_type(ns=0,id=0),eth(),eth_type(0x0800),ipv4(),udp(),
> packets:1, bytes:106, used:0.0s, offloaded:yes, dp:dummy, actions:2
> +])
> +AT_CHECK([ovs-appctl revalidator/resume])
> +AT_CHECK(
> + [ovs-appctl --format json dpif/offload/show \
> + | sed 's/.*"p1":{\([[^}]]*\)}.*/\1/; s/,/\n/g; s/"//g' \
> + | sed -n '/^rx_offload_/p' | sort], [0], [dnl
> +rx_offload_full:2
> +rx_offload_miss:2
> +rx_offload_partial:0
> +rx_offload_pipe_abort:1
> +])
> +
> +OVS_VSWITCHD_STOP
> +AT_CLEANUP
> +
> AT_SETUP([dpif-netdev - check dpctl/add-flow in_port exact match])
> OVS_VSWITCHD_START(
> [add-port br0 p1 \
> diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
> index 39e43d3768..04dcb9a8ee 100644
> --- a/tests/ofproto-dpif.at
> +++ b/tests/ofproto-dpif.at
> @@ -10196,6 +10196,18 @@ Globally enabled: false
> Datapaths:
> dummy@ovs-dummy:
> dummy
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> dummy_x
> - br0: port_no: 100
> - br1: port_no: 101
> @@ -10212,11 +10224,23 @@ AT_CHECK([ovs-appctl --format json --pretty
> dpif/offload/show], [0], [dnl
> "dummy": {
> "ports": {
> "br0": {
> - "port_no": 100},
> + "port_no": 100,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "br1": {
> - "port_no": 101},
> + "port_no": 101,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "ovs-dummy": {
> - "port_no": 0}}},
> + "port_no": 0,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0}}},
> "dummy_x": {
> }}},
> "enabled": false}
> @@ -10248,6 +10272,18 @@ Globally enabled: false
> Datapaths:
> dummy@ovs-dummy:
> dummy_x
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> + rx_offload_partial : 0
> + rx_offload_full : 0
> + rx_offload_miss : 0
> + rx_offload_pipe_abort: 0
> dummy
> - br0: port_no: 100
> - br1: port_no: 101
> @@ -10266,11 +10302,23 @@ AT_CHECK([ovs-appctl --format json --pretty
> dpif/offload/show], [0], [dnl
> "dummy_x": {
> "ports": {
> "br0": {
> - "port_no": 100},
> + "port_no": 100,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "br1": {
> - "port_no": 101},
> + "port_no": 101,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "ovs-dummy": {
> - "port_no": 0}}}}},
> + "port_no": 0,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0}}}}},
> "enabled": false}
> ])
>
> @@ -10302,17 +10350,37 @@ AT_CHECK([ovs-appctl --format json --pretty
> dpif/offload/show], [0], [dnl
> "dummy": {
> "ports": {
> "br0": {
> - "port_no": 100},
> + "port_no": 100,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "ovs-dummy": {
> - "port_no": 0},
> + "port_no": 0,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "p4": {
> - "port_no": 4}}},
> + "port_no": 4,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0}}},
> "dummy_x": {
> "ports": {
> "p1": {
> - "port_no": 1},
> + "port_no": 1,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0},
> "p3": {
> - "port_no": 3}}}}},
> + "port_no": 3,
> + "rx_offload_full": 0,
> + "rx_offload_miss": 0,
> + "rx_offload_partial": 0,
> + "rx_offload_pipe_abort": 0}}}}},
> "enabled": false}
> ])
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev