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]> --- lib/dpif-offload-dummy.c | 286 +++++++++++++++++++++++++++++++++++---- lib/dummy.h | 7 +- lib/netdev-dummy.c | 55 ++++++-- tests/dpif-netdev.at | 124 +++++++++++++++++ tests/ofproto-dpif.at | 90 ++++++++++-- 5 files changed, 506 insertions(+), 56 deletions(-) diff --git a/lib/dpif-offload-dummy.c b/lib/dpif-offload-dummy.c index a096e40d6d..3dd9eff14b 100644 --- a/lib/dpif-offload-dummy.c +++ b/lib/dpif-offload-dummy.c @@ -45,8 +45,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; @@ -66,6 +69,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, @@ -231,6 +247,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); } @@ -291,6 +308,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) { @@ -298,6 +316,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) { @@ -333,11 +357,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, @@ -448,15 +473,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); } @@ -466,11 +503,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); } } } @@ -518,6 +566,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, @@ -552,6 +613,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(); + return true; +} + static int dummy_flow_put(const struct dpif_offload *offload_, struct netdev *netdev, struct dpif_offload_flow_put *put, @@ -561,6 +702,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); @@ -569,6 +711,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( @@ -590,6 +736,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 @@ -614,11 +768,11 @@ 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; } +exit: dummy_offload_log_operation(modify ? "modify" : "add", error, put->ufid); return error; } @@ -653,6 +807,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); @@ -681,10 +839,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; } @@ -706,12 +860,15 @@ dummy_flow_stats(const struct dpif_offload *offload_, struct netdev *netdev, off_flow = dummy_find_offloaded_flow(port, ufid); 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; + attrs->dp_layer = off_flow->actions ? "dummy" : "ovs"; + attrs->dp_extra_info = NULL; + attrs->offloaded = true; + memcpy(stats, &off_flow->stats, sizeof *stats); + return true; } static void @@ -732,23 +889,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) { @@ -781,17 +941,83 @@ 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; } static void dummy_pmd_thread_work_cb(unsigned core_id OVS_UNUSED, int numa_id OVS_UNUSED, - void *ctx OVS_UNUSED) + void *ctx) { + struct dummy_offload *offload = ctx; + struct dpif_offload_port *port_; + COVERAGE_INC(dummy_offload_do_work); + + if (!offload) { + return; + } + + DPIF_OFFLOAD_PORT_FOR_EACH (port_, &offload->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->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); + } } static void @@ -814,9 +1040,9 @@ dummy_pmd_thread_lifecycle(const struct dpif_offload *dpif_offload, ovs_assert(!*callback || *callback == dummy_pmd_thread_work_cb); if (exit) { - free(*ctx); + *ctx = NULL; } else { - *ctx = *ctx ? *ctx : xstrdup("DUMMY_OFFLOAD_WORK"); + *ctx = dummy_offload_cast(dpif_offload); *callback = dummy_pmd_thread_work_cb; } } diff --git a/lib/dummy.h b/lib/dummy.h index e5c9d4071c..312fbed17c 100644 --- a/lib/dummy.h +++ b/lib/dummy.h @@ -41,10 +41,13 @@ 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 *); #endif /* dummy.h */ diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c index 7d3a7b9682..17ff436290 100644 --- a/lib/netdev-dummy.c +++ b/lib/netdev-dummy.c @@ -1875,7 +1875,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 +1886,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 1dca41c121..6390973ccb 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} ]) -- 2.52.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
