/*- * BSD LICENSE * * Copyright(c) 2010-2014 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "packet_burst_generator.h" #include "test.h" #define SLAVE_COUNT (4) #define RX_RING_SIZE 128 #define TX_RING_SIZE 512 #define MBUF_CACHE_SIZE (250) #define BURST_SIZE (32) #define TEST_RX_DESC_MAX (2048) #define TEST_TX_DESC_MAX (2048) #define MAX_PKT_BURST (32) #define DEF_PKT_BURST (16) #define BONDED_DEV_NAME ("unit_test_mode4_bond_dev") #define SLAVE_DEV_NAME_FMT ("unit_test_mode4_slave_%d") #define SLAVE_RX_QUEUE_FMT ("unit_test_mode4_slave_%d_rx") #define SLAVE_TX_QUEUE_FMT ("unit_test_mode4_slave_%d_tx") #define INVALID_SOCKET_ID (-1) #define INVALID_PORT_ID (0xFF) #define INVALID_BONDING_MODE (-1) static const struct ether_addr slave_mac_default = { { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00 } }; static const struct ether_addr parnter_mac_default = { { 0x22, 0xBB, 0xFF, 0xBB, 0x00, 0x00 } }; static const struct ether_addr parnter_system = { { 0x33, 0xFF, 0xBB, 0xFF, 0x00, 0x00 } }; static const struct ether_addr slow_protocol_mac_addr = { { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x02 } }; struct slave_conf { struct rte_ring *rx_queue; struct rte_ring *tx_queue; uint8_t port_id; uint8_t bonded : 1; uint8_t lacp_parnter_state; }; struct ether_vlan_hdr { struct ether_hdr pkt_eth_hdr; struct vlan_hdr vlan_hdr; }; struct link_bonding_unittest_params { uint8_t bonded_port_id; struct slave_conf slave_ports[SLAVE_COUNT]; struct rte_mempool *mbuf_pool; }; #define TEST_DEFAULT_SLAVE_COUNT RTE_DIM(test_params.slave_ports) #define TEST_RX_SLAVE_COUT TEST_DEFAULT_SLAVE_COUNT #define TEST_TX_SLAVE_COUNT TEST_DEFAULT_SLAVE_COUNT #define TEST_MARKER_SLAVE_COUT TEST_DEFAULT_SLAVE_COUNT #define TEST_EXPIRED_SLAVE_COUNT TEST_DEFAULT_SLAVE_COUNT #define TEST_PROMISC_SLAVE_COUNT TEST_DEFAULT_SLAVE_COUNT static struct link_bonding_unittest_params test_params = { .bonded_port_id = INVALID_PORT_ID, .slave_ports = { [0 ... SLAVE_COUNT - 1] = { .port_id = INVALID_PORT_ID} }, .mbuf_pool = NULL, }; static struct rte_eth_conf default_pmd_conf = { .rxmode = { .mq_mode = ETH_MQ_RX_NONE, .max_rx_pkt_len = ETHER_MAX_LEN, .split_hdr_size = 0, .header_split = 0, /**< Header Split disabled */ .hw_ip_checksum = 0, /**< IP checksum offload enabled */ .hw_vlan_filter = 0, /**< VLAN filtering disabled */ .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ .hw_strip_crc = 0, /**< CRC stripped by hardware */ }, .txmode = { .mq_mode = ETH_MQ_TX_NONE, }, .lpbk_mode = 0, }; #define FOR_EACH(_i, _item, _array, _size) \ for (_i = 0, _item = &_array[0]; _i < _size && (_item = &_array[_i]); _i++) /* Macro for iterating over every port that can be used as a slave * in this test. * _i variable used as an index in test_params->slave_ports * _slave pointer to &test_params->slave_ports[_idx] */ #define FOR_EACH_PORT(_i, _port) \ FOR_EACH(_i, _port, test_params.slave_ports, \ RTE_DIM(test_params.slave_ports)) /* Macro for iterating over every port that can be used as a slave * in this test and satisfy given condition. * * _i variable used as an index in test_params->slave_ports * _slave pointer to &test_params->slave_ports[_idx] * _condition condition that need to be checked */ #define FOR_EACH_PORT_IF(_i, _port, _condition) FOR_EACH_PORT((_i), (_port)) \ if (!!(_condition)) /* Macro for iterating over every port that is currently a slave of a bonded * device. * _i variable used as an index in test_params->slave_ports * _slave pointer to &test_params->slave_ports[_idx] * */ #define FOR_EACH_SLAVE(_i, _slave) \ FOR_EACH_PORT_IF(_i, _slave, (_slave)->bonded != 0) /* * Returns packets from slaves TX queue. * slave slave port * buffer for packets * size size of buffer * return number of packets or negative error number */ static int slave_get_pkts(struct slave_conf *slave, struct rte_mbuf **buf, uint16_t size) { return rte_ring_dequeue_burst(slave->tx_queue, (void **)buf, size); } /* * Injects given packets into slaves RX queue. * slave slave port * buffer for packets * size number of packets to be injected * return number of queued packets or negative error number */ static int slave_put_pkts(struct slave_conf *slave, struct rte_mbuf **buf, uint16_t size) { return rte_ring_enqueue_burst(slave->rx_queue, (void **)buf, size); } static uint16_t bond_rx(struct rte_mbuf **buf, uint16_t size) { return rte_eth_rx_burst(test_params.bonded_port_id, 0, buf, size); } static uint16_t bond_tx(struct rte_mbuf **buf, uint16_t size) { return rte_eth_tx_burst(test_params.bonded_port_id, 0, buf, size); } static void free_pkts(struct rte_mbuf **pkts, uint16_t count) { uint16_t i; for (i = 0; i < count; i++) { if (pkts[i] != NULL) rte_pktmbuf_free(pkts[i]); } } static int configure_ethdev(uint8_t port_id, uint8_t start) { TEST_ASSERT(rte_eth_dev_configure(port_id, 1, 1, &default_pmd_conf) == 0, "Failed to configure device %u", port_id); TEST_ASSERT(rte_eth_rx_queue_setup(port_id, 0, RX_RING_SIZE, rte_eth_dev_socket_id(port_id), NULL, test_params.mbuf_pool) == 0, "Failed to setup rx queue."); TEST_ASSERT(rte_eth_tx_queue_setup(port_id, 0, TX_RING_SIZE, rte_eth_dev_socket_id(port_id), NULL) == 0, "Failed to setup tx queue."); if (start) { TEST_ASSERT(rte_eth_dev_start(port_id) == 0, "Failed to start device (%d).", port_id); } return 0; } static int add_slave(struct slave_conf *slave, uint8_t start) { struct ether_addr addr, addr_check; /* Some sanity check */ RTE_VERIFY(test_params.slave_ports <= slave && slave - test_params.slave_ports < (int)RTE_DIM(test_params.slave_ports)); RTE_VERIFY(slave->bonded == 0); RTE_VERIFY(slave->port_id != INVALID_PORT_ID); ether_addr_copy(&slave_mac_default, &addr); addr.addr_bytes[ETHER_ADDR_LEN - 1] = slave->port_id; rte_eth_dev_mac_addr_remove(slave->port_id, &addr); TEST_ASSERT_SUCCESS(rte_eth_dev_mac_addr_add(slave->port_id, &addr, 0), "Failed to set slave MAC address"); TEST_ASSERT_SUCCESS(rte_eth_bond_slave_add(test_params.bonded_port_id, slave->port_id), "Failed to add slave (idx=%u, id=%u) to bonding (id=%u)", (uint8_t)(slave - test_params.slave_ports), slave->port_id, test_params.bonded_port_id); slave->bonded = 1; if (start) { TEST_ASSERT_SUCCESS(rte_eth_dev_start(slave->port_id), "Failed to start slave %u", slave->port_id); } rte_eth_macaddr_get(slave->port_id, &addr_check); TEST_ASSERT_EQUAL(is_same_ether_addr(&addr, &addr_check), 1, "Slave MAC address is not as expected"); RTE_VERIFY(slave->lacp_parnter_state == 0); return 0; } static int remove_slave(struct slave_conf *slave) { ptrdiff_t slave_idx = slave - test_params.slave_ports; RTE_VERIFY(test_params.slave_ports <= slave && slave_idx < (ptrdiff_t)RTE_DIM(test_params.slave_ports)); RTE_VERIFY(slave->bonded == 1); RTE_VERIFY(slave->port_id != INVALID_PORT_ID); TEST_ASSERT_EQUAL(rte_ring_count(slave->rx_queue), 0, "Slave %u tx queue not empty while removing from bonding.", slave->port_id); TEST_ASSERT_EQUAL(rte_ring_count(slave->rx_queue), 0, "Slave %u tx queue not empty while removing from bonding.", slave->port_id); TEST_ASSERT_EQUAL(rte_eth_bond_slave_remove(test_params.bonded_port_id, slave->port_id), 0, "Failed to remove slave (idx=%u, id=%u) from bonding (id=%u)", (uint8_t)slave_idx, slave->port_id, test_params.bonded_port_id); slave->bonded = 0; slave->lacp_parnter_state = 0; return 0; } static int initialize_bonded_device_with_slaves(uint8_t slave_count, uint8_t start) { uint8_t i; RTE_VERIFY(test_params.bonded_port_id != INVALID_PORT_ID); for (i = 0; i < slave_count; i++) { TEST_ASSERT_SUCCESS(add_slave(&test_params.slave_ports[i], 1), "Failed to add port %u to bonded device.\n", test_params.slave_ports[i].port_id); } /* Reset mode 4 configuration */ rte_eth_bond_8023ad_setup(test_params.bonded_port_id, NULL); rte_eth_promiscuous_disable(test_params.bonded_port_id); if (start) TEST_ASSERT_SUCCESS(rte_eth_dev_start(test_params.bonded_port_id), "Failed to start bonded device"); return TEST_SUCCESS; } static int remove_slaves_and_stop_bonded_device(void) { struct slave_conf *slave; int retval; uint8_t slaves[RTE_MAX_ETHPORTS]; uint8_t i; rte_eth_dev_stop(test_params.bonded_port_id); FOR_EACH_SLAVE(i, slave) remove_slave(slave); retval = rte_eth_bond_slaves_get(test_params.bonded_port_id, slaves, RTE_DIM(slaves)); TEST_ASSERT_EQUAL(retval, 0, "Expected bonded device %u have 0 slaves but returned %d.", test_params.bonded_port_id, retval); FOR_EACH_PORT(i, slave) { rte_eth_dev_stop(slave->port_id); TEST_ASSERT(slave->bonded == 0, "Port id=%u is still marked as enslaved.", slave->port_id); } return TEST_SUCCESS; } static int test_setup(void) { int retval, nb_mbuf_per_pool; char name[RTE_ETH_NAME_MAX_LEN]; struct slave_conf *port; const uint8_t socket_id = rte_socket_id(); uint8_t i; if (test_params.mbuf_pool == NULL) { nb_mbuf_per_pool = TEST_RX_DESC_MAX + DEF_PKT_BURST + TEST_TX_DESC_MAX + MAX_PKT_BURST; test_params.mbuf_pool = rte_pktmbuf_pool_create("TEST_MODE4", nb_mbuf_per_pool, MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, socket_id); TEST_ASSERT(test_params.mbuf_pool != NULL, "rte_mempool_create failed\n"); } /* Create / initialize ring eth devs. */ FOR_EACH_PORT(i, port) { port = &test_params.slave_ports[i]; if (port->rx_queue == NULL) { retval = snprintf(name, RTE_DIM(name), SLAVE_RX_QUEUE_FMT, i); TEST_ASSERT(retval <= (int)RTE_DIM(name) - 1, "Name too long"); port->rx_queue = rte_ring_create(name, RX_RING_SIZE, socket_id, 0); TEST_ASSERT(port->rx_queue != NULL, "Failed to allocate rx ring '%s': %s", name, rte_strerror(rte_errno)); } if (port->tx_queue == NULL) { retval = snprintf(name, RTE_DIM(name), SLAVE_TX_QUEUE_FMT, i); TEST_ASSERT(retval <= (int)RTE_DIM(name) - 1, "Name too long"); port->tx_queue = rte_ring_create(name, TX_RING_SIZE, socket_id, 0); TEST_ASSERT_NOT_NULL(port->tx_queue, "Failed to allocate tx ring '%s': %s", name, rte_strerror(rte_errno)); } if (port->port_id == INVALID_PORT_ID) { retval = snprintf(name, RTE_DIM(name), SLAVE_DEV_NAME_FMT, i); TEST_ASSERT(retval < (int)RTE_DIM(name) - 1, "Name too long"); retval = rte_eth_from_rings(name, &port->rx_queue, 1, &port->tx_queue, 1, socket_id); TEST_ASSERT(retval >= 0, "Failed to create ring ethdev '%s'\n", name); port->port_id = rte_eth_dev_count() - 1; } retval = configure_ethdev(port->port_id, 1); TEST_ASSERT_SUCCESS(retval, "Failed to configure virtual ethdev %s\n", name); } if (test_params.bonded_port_id == INVALID_PORT_ID) { retval = rte_eth_bond_create(BONDED_DEV_NAME, BONDING_MODE_8023AD, socket_id); TEST_ASSERT(retval >= 0, "Failed to create bonded ethdev %s", BONDED_DEV_NAME); test_params.bonded_port_id = retval; TEST_ASSERT_SUCCESS(configure_ethdev(test_params.bonded_port_id, 0), "Failed to configure bonded ethdev %s", BONDED_DEV_NAME); } else if (rte_eth_bond_mode_get(test_params.bonded_port_id) != BONDING_MODE_8023AD) { TEST_ASSERT(rte_eth_bond_mode_set(test_params.bonded_port_id, BONDING_MODE_8023AD) == 0, "Failed to set ethdev %d to mode %d", test_params.bonded_port_id, BONDING_MODE_8023AD); } return 0; } static void testsuite_teardown(void) { struct slave_conf *port; uint8_t i; /* Only stop ports. * Any cleanup/reset state is done when particular test is * started. */ rte_eth_dev_stop(test_params.bonded_port_id); FOR_EACH_PORT(i, port) rte_eth_dev_stop(port->port_id); } /* * Check if given LACP packet. If it is, make make replay packet to force * COLLECTING state. * return 0 when pkt is LACP frame, 1 if it is not slow frame, 2 if it is slow * frame but not LACP */ static int make_lacp_reply(struct slave_conf *slave, struct rte_mbuf *pkt) { struct ether_hdr *hdr; struct slow_protocol_frame *slow_hdr; struct lacpdu *lacp; /* look for LACP */ hdr = rte_pktmbuf_mtod(pkt, struct ether_hdr *); if (hdr->ether_type != rte_cpu_to_be_16(ETHER_TYPE_SLOW)) return 1; slow_hdr = rte_pktmbuf_mtod(pkt, struct slow_protocol_frame *); /* ignore packets of other types */ if (slow_hdr->slow_protocol.subtype != SLOW_SUBTYPE_LACP) return 2; slow_hdr = rte_pktmbuf_mtod(pkt, struct slow_protocol_frame *); /* Change source address to partner address */ ether_addr_copy(&parnter_mac_default, &slow_hdr->eth_hdr.s_addr); slow_hdr->eth_hdr.s_addr.addr_bytes[ETHER_ADDR_LEN - 1] = slave->port_id; lacp = (struct lacpdu *) &slow_hdr->slow_protocol; /* Save last received state */ slave->lacp_parnter_state = lacp->actor.state; /* Change it into LACP replay by matching parameters. */ memcpy(&lacp->partner.port_params, &lacp->actor.port_params, sizeof(struct port_params)); lacp->partner.state = lacp->actor.state; ether_addr_copy(&parnter_system, &lacp->actor.port_params.system); lacp->actor.state = STATE_LACP_ACTIVE | STATE_SYNCHRONIZATION | STATE_AGGREGATION | STATE_COLLECTING | STATE_DISTRIBUTING; return 0; } /* * Reads packets from given slave, search for LACP packet and reply them. * * Receives burst of packets from slave. Looks for LACP packet. Drops * all other packets. Prepares response LACP and sends it back. * * return number of LACP received and replied, -1 on error. */ static int bond_handshake_reply(struct slave_conf *slave) { int retval; struct rte_mbuf *rx_buf[MAX_PKT_BURST]; struct rte_mbuf *lacp_tx_buf[MAX_PKT_BURST]; uint16_t lacp_tx_buf_cnt = 0, i; retval = slave_get_pkts(slave, rx_buf, RTE_DIM(rx_buf)); TEST_ASSERT(retval >= 0, "Getting slave %u packets failed.", slave->port_id); for (i = 0; i < (uint16_t)retval; i++) { if (make_lacp_reply(slave, rx_buf[i]) == 0) { /* reply with actor's LACP */ lacp_tx_buf[lacp_tx_buf_cnt++] = rx_buf[i]; } else rte_pktmbuf_free(rx_buf[i]); } if (lacp_tx_buf_cnt == 0) return 0; retval = slave_put_pkts(slave, lacp_tx_buf, lacp_tx_buf_cnt); if (retval <= lacp_tx_buf_cnt) { /* retval might be negative */ for (i = RTE_MAX(0, retval); retval < lacp_tx_buf_cnt; retval++) rte_pktmbuf_free(lacp_tx_buf[i]); } TEST_ASSERT_EQUAL(retval, lacp_tx_buf_cnt, "Failed to equeue lacp packets into slave %u tx queue.", slave->port_id); return lacp_tx_buf_cnt; } /* * Function check if given slave tx queue contains packets that make mode 4 * handshake complete. It will drain slave queue. * return 0 if handshake not completed, 1 if handshake was complete, */ static int bond_handshake_done(struct slave_conf *slave) { const uint8_t expected_state = STATE_LACP_ACTIVE | STATE_SYNCHRONIZATION | STATE_AGGREGATION | STATE_COLLECTING | STATE_DISTRIBUTING; return slave->lacp_parnter_state == expected_state; } static unsigned bond_get_update_timeout_ms(void) { struct rte_eth_bond_8023ad_conf conf; rte_eth_bond_8023ad_conf_get(test_params.bonded_port_id, &conf); return conf.update_timeout_ms; } /* * Exchanges LACP packets with partner to achieve dynamic port configuration. * return TEST_SUCCESS if initial handshake succeed, TEST_FAILED otherwise. */ static int bond_handshake(void) { struct slave_conf *slave; struct rte_mbuf *buf[MAX_PKT_BURST]; uint16_t nb_pkts; uint8_t all_slaves_done, i, j; uint8_t status[RTE_DIM(test_params.slave_ports)] = { 0 }; const unsigned delay = bond_get_update_timeout_ms(); /* Exchange LACP frames */ all_slaves_done = 0; for (i = 0; i < 30 && all_slaves_done == 0; ++i) { rte_delay_ms(delay); all_slaves_done = 1; FOR_EACH_SLAVE(j, slave) { /* If response already send, skip slave */ if (status[j] != 0) continue; if (bond_handshake_reply(slave) < 0) { all_slaves_done = 0; break; } status[j] = bond_handshake_done(slave); if (status[j] == 0) all_slaves_done = 0; } nb_pkts = bond_tx(NULL, 0); TEST_ASSERT_EQUAL(nb_pkts, 0, "Packets transmitted unexpectedly"); nb_pkts = bond_rx(buf, RTE_DIM(buf)); free_pkts(buf, nb_pkts); TEST_ASSERT_EQUAL(nb_pkts, 0, "Packets received unexpectedly"); } /* If response didn't send - report failure */ TEST_ASSERT_EQUAL(all_slaves_done, 1, "Bond handshake failed\n"); /* If flags doesn't match - report failure */ return all_slaves_done = 1 ? TEST_SUCCESS : TEST_FAILED; } #define TEST_LACP_SLAVE_COUT RTE_DIM(test_params.slave_ports) static int test_mode4_lacp(void) { int retval; retval = initialize_bonded_device_with_slaves(TEST_LACP_SLAVE_COUT, 1); TEST_ASSERT_SUCCESS(retval, "Failed to initialize bonded device"); /* Test LACP handshake function */ retval = bond_handshake(); TEST_ASSERT_SUCCESS(retval, "Initial handshake failed"); retval = remove_slaves_and_stop_bonded_device(); TEST_ASSERT_SUCCESS(retval, "Test cleanup failed."); return TEST_SUCCESS; } static int generate_packets(struct ether_addr *src_mac, struct ether_addr *dst_mac, uint16_t count, struct rte_mbuf **buf) { uint16_t pktlen = PACKET_BURST_GEN_PKT_LEN; uint8_t vlan_enable = 0; uint16_t vlan_id = 0; uint8_t ip4_type = 1; /* 0 - ipv6 */ uint16_t src_port = 10, dst_port = 20; uint32_t ip_src[4] = { [0 ... 2] = 0xDEADBEEF, [3] = IPv4(192, 168, 0, 1) }; uint32_t ip_dst[4] = { [0 ... 2] = 0xFEEDFACE, [3] = IPv4(192, 168, 0, 2) }; struct ether_hdr pkt_eth_hdr; struct udp_hdr pkt_udp_hdr; union { struct ipv4_hdr v4; struct ipv6_hdr v6; } pkt_ip_hdr; int retval; initialize_eth_header(&pkt_eth_hdr, src_mac, dst_mac, ip4_type, vlan_enable, vlan_id); if (ip4_type) initialize_ipv4_header(&pkt_ip_hdr.v4, ip_src[3], ip_dst[3], pktlen); else initialize_ipv6_header(&pkt_ip_hdr.v6, (uint8_t *)ip_src, (uint8_t *)&ip_dst, pktlen); initialize_udp_header(&pkt_udp_hdr, src_port, dst_port, 16); retval = generate_packet_burst(test_params.mbuf_pool, buf, &pkt_eth_hdr, vlan_enable, &pkt_ip_hdr, 1, &pkt_udp_hdr, count, pktlen, 1); if (retval > 0 && retval != count) free_pkts(&buf[count - retval], retval); TEST_ASSERT_EQUAL(retval, count, "Failed to generate %u packets", count); return count; } static int generate_and_put_packets(struct slave_conf *slave, struct ether_addr *src_mac, struct ether_addr *dst_mac, uint16_t count) { struct rte_mbuf *pkts[MAX_PKT_BURST]; int retval; retval = generate_packets(src_mac, dst_mac, count, pkts); if (retval != (int)count) return retval; retval = slave_put_pkts(slave, pkts, count); if (retval > 0 && retval != count) free_pkts(&pkts[retval], count - retval); TEST_ASSERT_EQUAL(retval, count, "Failed to enqueue packets into slave %u RX queue", slave->port_id); return TEST_SUCCESS; } static int test_mode4_rx(void) { struct slave_conf *slave; uint16_t i, j; uint16_t expected_pkts_cnt; struct rte_mbuf *pkts[MAX_PKT_BURST]; int retval; unsigned delay; struct ether_hdr *hdr; struct ether_addr src_mac = { { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00 } }; struct ether_addr dst_mac; struct ether_addr bonded_mac; retval = initialize_bonded_device_with_slaves(TEST_PROMISC_SLAVE_COUNT, 1); TEST_ASSERT_SUCCESS(retval, "Failed to initialize bonded device"); retval = bond_handshake(); TEST_ASSERT_SUCCESS(retval, "Initial handshake failed"); rte_eth_macaddr_get(test_params.bonded_port_id, &bonded_mac); ether_addr_copy(&bonded_mac, &dst_mac); /* Assert that dst address is not bonding address. Do not set the * least significant bit of the zero byte as this would create a * multicast address. */ dst_mac.addr_bytes[0] += 2; /* First try with promiscuous mode enabled. * Add 2 packets to each slave. First with bonding MAC address, second with * different. Check if we received all of them. */ rte_eth_promiscuous_enable(test_params.bonded_port_id); expected_pkts_cnt = 0; FOR_EACH_SLAVE(i, slave) { retval = generate_and_put_packets(slave, &src_mac, &bonded_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to enqueue packets to slave %u", slave->port_id); retval = generate_and_put_packets(slave, &src_mac, &dst_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to enqueue packets to slave %u", slave->port_id); /* Expect 2 packets per slave */ expected_pkts_cnt += 2; } retval = rte_eth_rx_burst(test_params.bonded_port_id, 0, pkts, RTE_DIM(pkts)); if (retval == expected_pkts_cnt) { int cnt[2] = { 0, 0 }; for (i = 0; i < expected_pkts_cnt; i++) { hdr = rte_pktmbuf_mtod(pkts[i], struct ether_hdr *); cnt[is_same_ether_addr(&hdr->d_addr, &bonded_mac)]++; } free_pkts(pkts, expected_pkts_cnt); /* For division by 2 expected_pkts_cnt must be even */ RTE_VERIFY((expected_pkts_cnt & 1) == 0); TEST_ASSERT(cnt[0] == expected_pkts_cnt / 2 && cnt[1] == expected_pkts_cnt / 2, "Expected %u packets with the same MAC and %u with different but " "got %u with the same and %u with diffrent MAC", expected_pkts_cnt / 2, expected_pkts_cnt / 2, cnt[1], cnt[0]); } else if (retval > 0) free_pkts(pkts, retval); TEST_ASSERT_EQUAL(retval, expected_pkts_cnt, "Expected %u packets but received only %d", expected_pkts_cnt, retval); /* Now, disable promiscuous mode. When promiscuous mode is disabled we * expect to receive only packets that are directed to bonding port. */ rte_eth_promiscuous_disable(test_params.bonded_port_id); expected_pkts_cnt = 0; FOR_EACH_SLAVE(i, slave) { retval = generate_and_put_packets(slave, &src_mac, &bonded_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to enqueue packets to slave %u", slave->port_id); retval = generate_and_put_packets(slave, &src_mac, &dst_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to enqueue packets to slave %u", slave->port_id); /* Expect only one packet per slave */ expected_pkts_cnt += 1; } retval = rte_eth_rx_burst(test_params.bonded_port_id, 0, pkts, RTE_DIM(pkts)); if (retval == expected_pkts_cnt) { int eq_cnt = 0; for (i = 0; i < expected_pkts_cnt; i++) { hdr = rte_pktmbuf_mtod(pkts[i], struct ether_hdr *); eq_cnt += is_same_ether_addr(&hdr->d_addr, &bonded_mac); } free_pkts(pkts, expected_pkts_cnt); TEST_ASSERT_EQUAL(eq_cnt, expected_pkts_cnt, "Packet address mismatch"); } else if (retval > 0) free_pkts(pkts, retval); TEST_ASSERT_EQUAL(retval, expected_pkts_cnt, "Expected %u packets but received only %d", expected_pkts_cnt, retval); /* Link down test: simulate link down for first slave. */ delay = bond_get_update_timeout_ms(); uint8_t slave_down_id = INVALID_PORT_ID; /* Find first slave and make link down on it*/ FOR_EACH_SLAVE(i, slave) { rte_eth_dev_set_link_down(slave->port_id); slave_down_id = slave->port_id; break; } RTE_VERIFY(slave_down_id != INVALID_PORT_ID); /* Give some time to rearrange bonding */ for (i = 0; i < 3; i++) { rte_delay_ms(delay); bond_handshake(); } TEST_ASSERT_SUCCESS(bond_handshake(), "Handshake after link down failed"); /* Put packet to each slave */ FOR_EACH_SLAVE(i, slave) { void *pkt = NULL; dst_mac.addr_bytes[ETHER_ADDR_LEN - 1] = slave->port_id; retval = generate_and_put_packets(slave, &src_mac, &dst_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to generate test packet burst."); src_mac.addr_bytes[ETHER_ADDR_LEN - 1] = slave->port_id; retval = generate_and_put_packets(slave, &src_mac, &bonded_mac, 1); TEST_ASSERT_SUCCESS(retval, "Failed to generate test packet burst."); retval = bond_rx(pkts, RTE_DIM(pkts)); /* Clean anything */ if (retval > 0) free_pkts(pkts, retval); while (rte_ring_dequeue(slave->rx_queue, (void **)&pkt) == 0) rte_pktmbuf_free(pkt); if (slave_down_id == slave->port_id) TEST_ASSERT_EQUAL(retval, 0, "Packets received unexpectedly."); else TEST_ASSERT_NOT_EQUAL(retval, 0, "Expected to receive some packets on slave %u.", slave->port_id); rte_eth_dev_start(slave->port_id); for (j = 0; j < 5; j++) { TEST_ASSERT(bond_handshake_reply(slave) >= 0, "Handshake after link up"); if (bond_handshake_done(slave) == 1) break; } TEST_ASSERT(j < 5, "Failed to agregate slave after link up"); } remove_slaves_and_stop_bonded_device(); return TEST_SUCCESS; } static int test_mode4_tx_burst(void) { struct slave_conf *slave; uint16_t i, j; uint16_t exp_pkts_cnt, pkts_cnt = 0; struct rte_mbuf *pkts[MAX_PKT_BURST]; int retval; unsigned delay; struct ether_addr dst_mac = { { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00 } }; struct ether_addr bonded_mac; retval = initialize_bonded_device_with_slaves(TEST_TX_SLAVE_COUNT, 1); TEST_ASSERT_SUCCESS(retval, "Failed to initialize bonded device"); retval = bond_handshake(); TEST_ASSERT_SUCCESS(retval, "Initial handshake failed"); rte_eth_macaddr_get(test_params.bonded_port_id, &bonded_mac); /* Prepare burst */ for (pkts_cnt = 0; pkts_cnt < RTE_DIM(pkts); pkts_cnt++) { dst_mac.addr_bytes[ETHER_ADDR_LEN - 1] = pkts_cnt; retval = generate_packets(&bonded_mac, &dst_mac, 1, &pkts[pkts_cnt]); if (retval != 1) free_pkts(pkts, pkts_cnt); TEST_ASSERT_EQUAL(retval, 1, "Failed to generate packet %u", pkts_cnt); } exp_pkts_cnt = pkts_cnt; /* Transmit packets on bonded device */ retval = bond_tx(pkts, pkts_cnt); if (retval > 0 && retval < pkts_cnt) free_pkts(&pkts[retval], pkts_cnt - retval); TEST_ASSERT_EQUAL(retval, pkts_cnt, "TX on bonded device failed"); /* Check if packets were transmitted properly. Every slave should have * at least one packet, and sum must match. Under normal operation * there should be no LACP nor MARKER frames. */ pkts_cnt = 0; FOR_EACH_SLAVE(i, slave) { uint16_t normal_cnt, slow_cnt; retval = slave_get_pkts(slave, pkts, RTE_DIM(pkts)); normal_cnt = 0; slow_cnt = 0; for (j = 0; j < retval; j++) { if (make_lacp_reply(slave, pkts[j]) == 1) normal_cnt++; else slow_cnt++; } free_pkts(pkts, normal_cnt + slow_cnt); TEST_ASSERT_EQUAL(slow_cnt, 0, "slave %u unexpectedly transmitted %d SLOW packets", slave->port_id, slow_cnt); TEST_ASSERT_NOT_EQUAL(normal_cnt, 0, "slave %u did not transmitted any packets", slave->port_id); pkts_cnt += normal_cnt; } TEST_ASSERT_EQUAL(exp_pkts_cnt, pkts_cnt, "Expected %u packets but transmitted only %d", exp_pkts_cnt, pkts_cnt); /* Link down test: * simulate link down for first slave. */ delay = bond_get_update_timeout_ms(); uint8_t slave_down_id = INVALID_PORT_ID; FOR_EACH_SLAVE(i, slave) { rte_eth_dev_set_link_down(slave->port_id); slave_down_id = slave->port_id; break; } RTE_VERIFY(slave_down_id != INVALID_PORT_ID); /* Give some time to rearrange bonding. */ for (i = 0; i < 3; i++) { bond_handshake(); rte_delay_ms(delay); } TEST_ASSERT_SUCCESS(bond_handshake(), "Handshake after link down failed"); /* Prepare burst. */ for (pkts_cnt = 0; pkts_cnt < RTE_DIM(pkts); pkts_cnt++) { dst_mac.addr_bytes[ETHER_ADDR_LEN - 1] = pkts_cnt; retval = generate_packets(&bonded_mac, &dst_mac, 1, &pkts[pkts_cnt]); if (retval != 1) free_pkts(pkts, pkts_cnt); TEST_ASSERT_EQUAL(retval, 1, "Failed to generate test packet %u", pkts_cnt); } exp_pkts_cnt = pkts_cnt; /* Transmit packets on bonded device. */ retval = bond_tx(pkts, pkts_cnt); if (retval > 0 && retval < pkts_cnt) free_pkts(&pkts[retval], pkts_cnt - retval); TEST_ASSERT_EQUAL(retval, pkts_cnt, "TX on bonded device failed"); /* Check if packets was transmitted properly. Every slave should have * at least one packet, and sum must match. Under normal operation * there should be no LACP nor MARKER frames. */ pkts_cnt = 0; FOR_EACH_SLAVE(i, slave) { uint16_t normal_cnt, slow_cnt; retval = slave_get_pkts(slave, pkts, RTE_DIM(pkts)); normal_cnt = 0; slow_cnt = 0; for (j = 0; j < retval; j++) { if (make_lacp_reply(slave, pkts[j]) == 1) normal_cnt++; else slow_cnt++; } free_pkts(pkts, normal_cnt + slow_cnt); if (slave_down_id == slave->port_id) { TEST_ASSERT_EQUAL(normal_cnt + slow_cnt, 0, "slave %u enexpectedly transmitted %u packets", normal_cnt + slow_cnt, slave->port_id); } else { TEST_ASSERT_EQUAL(slow_cnt, 0, "slave %u unexpectedly transmitted %d SLOW packets", slave->port_id, slow_cnt); TEST_ASSERT_NOT_EQUAL(normal_cnt, 0, "slave %u did not transmitted any packets", slave->port_id); } pkts_cnt += normal_cnt; } TEST_ASSERT_EQUAL(exp_pkts_cnt, pkts_cnt, "Expected %u packets but transmitted only %d", exp_pkts_cnt, pkts_cnt); return remove_slaves_and_stop_bonded_device(); } static void init_marker(struct rte_mbuf *pkt, struct slave_conf *slave) { struct marker_header *marker_hdr = rte_pktmbuf_mtod(pkt, struct marker_header *); /* Copy multicast destination address */ ether_addr_copy(&slow_protocol_mac_addr, &marker_hdr->eth_hdr.d_addr); /* Init source address */ ether_addr_copy(&parnter_mac_default, &marker_hdr->eth_hdr.s_addr); marker_hdr->eth_hdr.s_addr.addr_bytes[ETHER_ADDR_LEN-1] = slave->port_id; marker_hdr->eth_hdr.ether_type = rte_cpu_to_be_16(ETHER_TYPE_SLOW); marker_hdr->marker.subtype = SLOW_SUBTYPE_MARKER; marker_hdr->marker.version_number = 1; marker_hdr->marker.tlv_type_marker = MARKER_TLV_TYPE_INFO; marker_hdr->marker.info_length = offsetof(struct marker, reserved_90) - offsetof(struct marker, requester_port); RTE_VERIFY(marker_hdr->marker.info_length == 16); marker_hdr->marker.requester_port = slave->port_id + 1; marker_hdr->marker.tlv_type_terminator = TLV_TYPE_TERMINATOR_INFORMATION; marker_hdr->marker.terminator_length = 0; } static int test_mode4_marker(void) { struct slave_conf *slave; struct rte_mbuf *pkts[MAX_PKT_BURST]; struct rte_mbuf *marker_pkt; struct marker_header *marker_hdr; unsigned delay; int retval; uint16_t nb_pkts; uint8_t i, j; const uint16_t ethtype_slow_be = rte_be_to_cpu_16(ETHER_TYPE_SLOW); retval = initialize_bonded_device_with_slaves(TEST_MARKER_SLAVE_COUT, 1); TEST_ASSERT_SUCCESS(retval, "Failed to initialize bonded device"); /* Test LACP handshake function */ retval = bond_handshake(); TEST_ASSERT_SUCCESS(retval, "Initial handshake failed"); delay = bond_get_update_timeout_ms(); FOR_EACH_SLAVE(i, slave) { marker_pkt = rte_pktmbuf_alloc(test_params.mbuf_pool); TEST_ASSERT_NOT_NULL(marker_pkt, "Failed to allocate marker packet"); init_marker(marker_pkt, slave); retval = slave_put_pkts(slave, &marker_pkt, 1); if (retval != 1) rte_pktmbuf_free(marker_pkt); TEST_ASSERT_EQUAL(retval, 1, "Failed to send marker packet to slave %u", slave->port_id); for (j = 0; j < 20; ++j) { rte_delay_ms(delay); retval = rte_eth_rx_burst(test_params.bonded_port_id, 0, pkts, RTE_DIM(pkts)); if (retval > 0) free_pkts(pkts, retval); TEST_ASSERT_EQUAL(retval, 0, "Received packets unexpectedly"); retval = rte_eth_tx_burst(test_params.bonded_port_id, 0, NULL, 0); TEST_ASSERT_EQUAL(retval, 0, "Requested TX of 0 packets but %d transmitted", retval); /* Check if LACP packet was send by state machines First and only packet must be a maker response */ retval = slave_get_pkts(slave, pkts, MAX_PKT_BURST); if (retval == 0) continue; if (retval > 1) free_pkts(pkts, retval); TEST_ASSERT_EQUAL(retval, 1, "failed to get slave packets"); nb_pkts = retval; marker_hdr = rte_pktmbuf_mtod(pkts[0], struct marker_header *); /* Check if it's slow packet*/ if (marker_hdr->eth_hdr.ether_type != ethtype_slow_be) retval = -1; /* Check if it's marker packet */ else if (marker_hdr->marker.subtype != SLOW_SUBTYPE_MARKER) retval = -2; else if (marker_hdr->marker.tlv_type_marker != MARKER_TLV_TYPE_RESP) retval = -3; free_pkts(pkts, nb_pkts); TEST_ASSERT_NOT_EQUAL(retval, -1, "Unexpected protocol type"); TEST_ASSERT_NOT_EQUAL(retval, -2, "Unexpected sub protocol type"); TEST_ASSERT_NOT_EQUAL(retval, -3, "Unexpected marker type"); break; } TEST_ASSERT(j < 20, "Marker response not found"); } retval = remove_slaves_and_stop_bonded_device(); TEST_ASSERT_SUCCESS(retval, "Test cleanup failed."); return TEST_SUCCESS; } static int test_mode4_expired(void) { struct slave_conf *slave, *exp_slave = NULL; struct rte_mbuf *pkts[MAX_PKT_BURST]; int retval; uint32_t old_delay; uint8_t i; uint16_t j; struct rte_eth_bond_8023ad_conf conf; retval = initialize_bonded_device_with_slaves(TEST_EXPIRED_SLAVE_COUNT, 1); /* Set custom timeouts to make test last shorter. */ rte_eth_bond_8023ad_conf_get(test_params.bonded_port_id, &conf); conf.fast_periodic_ms = 100; conf.slow_periodic_ms = 600; conf.short_timeout_ms = 300; conf.long_timeout_ms = 900; conf.aggregate_wait_timeout_ms = 200; conf.tx_period_ms = 100; old_delay = conf.update_timeout_ms; conf.update_timeout_ms = 10; rte_eth_bond_8023ad_setup(test_params.bonded_port_id, &conf); /* Wait for new settings to be applied. */ for (i = 0; i < old_delay/conf.update_timeout_ms * 2; i++) { FOR_EACH_SLAVE(j, slave) bond_handshake_reply(slave); rte_delay_ms(conf.update_timeout_ms); } retval = bond_handshake(); TEST_ASSERT_SUCCESS(retval, "Initial handshake failed"); /* Find first slave */ FOR_EACH_SLAVE(i, slave) { exp_slave = slave; break; } RTE_VERIFY(exp_slave != NULL); /* When one of partners do not send or respond to LACP frame in * conf.long_timeout_ms time, internal state machines should detect this * and transit to expired state. */ for (j = 0; j < conf.long_timeout_ms/conf.update_timeout_ms + 2; j++) { rte_delay_ms(conf.update_timeout_ms); retval = bond_tx(NULL, 0); TEST_ASSERT_EQUAL(retval, 0, "Unexpectedly received %d packets", retval); FOR_EACH_SLAVE(i, slave) { retval = bond_handshake_reply(slave); TEST_ASSERT(retval >= 0, "Handshake failed"); /* Remove replay for slave that supose to be expired. */ if (slave == exp_slave) { while (rte_ring_count(slave->rx_queue) > 0) { void *pkt = NULL; rte_ring_dequeue(slave->rx_queue, &pkt); rte_pktmbuf_free(pkt); } } } retval = bond_rx(pkts, RTE_DIM(pkts)); if (retval > 0) free_pkts(pkts, retval); TEST_ASSERT_EQUAL(retval, 0, "Unexpectedly received %d packets", retval); } /* After test only expected slave should be in EXPIRED state */ FOR_EACH_SLAVE(i, slave) { if (slave == exp_slave) TEST_ASSERT(slave->lacp_parnter_state & STATE_EXPIRED, "Slave %u should be in expired.", slave->port_id); else TEST_ASSERT_EQUAL(bond_handshake_done(slave), 1, "Slave %u should be operational.", slave->port_id); } retval = remove_slaves_and_stop_bonded_device(); TEST_ASSERT_SUCCESS(retval, "Test cleanup failed."); return TEST_SUCCESS; } static int check_environment(void) { struct slave_conf *port; uint8_t i, env_state; uint8_t slaves[RTE_DIM(test_params.slave_ports)]; int slaves_count; env_state = 0; FOR_EACH_PORT(i, port) { if (rte_ring_count(port->rx_queue) != 0) env_state |= 0x01; if (rte_ring_count(port->tx_queue) != 0) env_state |= 0x02; if (port->bonded != 0) env_state |= 0x04; if (port->lacp_parnter_state != 0) env_state |= 0x08; if (env_state != 0) break; } slaves_count = rte_eth_bond_slaves_get(test_params.bonded_port_id, slaves, RTE_DIM(slaves)); if (slaves_count != 0) env_state |= 0x10; TEST_ASSERT_EQUAL(env_state, 0, "Environment not clean (port %u):%s%s%s%s%s", port->port_id, env_state & 0x01 ? " slave rx queue not clean" : "", env_state & 0x02 ? " slave tx queue not clean" : "", env_state & 0x04 ? " port marked as enslaved" : "", env_state & 0x80 ? " slave state is not reset" : "", env_state & 0x10 ? " slave count not equal 0" : "."); return TEST_SUCCESS; } static int test_mode4_executor(int (*test_func)(void)) { struct slave_conf *port; int test_result; uint8_t i; void *pkt; /* Check if environment is clean. Fail to launch a test if there was * a critical error before that prevented to reset environment. */ TEST_ASSERT_SUCCESS(check_environment(), "Refusing to launch test in dirty environment."); RTE_VERIFY(test_func != NULL); test_result = (*test_func)(); /* If test succeed check if environment wast left in good condition. */ if (test_result == TEST_SUCCESS) test_result = check_environment(); /* Reset environment in case test failed to do that. */ if (test_result != TEST_SUCCESS) { TEST_ASSERT_SUCCESS(remove_slaves_and_stop_bonded_device(), "Failed to stop bonded device"); FOR_EACH_PORT(i, port) { while (rte_ring_count(port->rx_queue) != 0) { if (rte_ring_dequeue(port->rx_queue, &pkt) == 0) rte_pktmbuf_free(pkt); } while (rte_ring_count(port->tx_queue) != 0) { if (rte_ring_dequeue(port->tx_queue, &pkt) == 0) rte_pktmbuf_free(pkt); } } } return test_result; } static int test_mode4_lacp_wrapper(void) { return test_mode4_executor(&test_mode4_lacp); } static int test_mode4_marker_wrapper(void) { return test_mode4_executor(&test_mode4_marker); } static int test_mode4_rx_wrapper(void) { return test_mode4_executor(&test_mode4_rx); } static int test_mode4_tx_burst_wrapper(void) { return test_mode4_executor(&test_mode4_tx_burst); } static int test_mode4_expired_wrapper(void) { return test_mode4_executor(&test_mode4_expired); } static struct unit_test_suite link_bonding_mode4_test_suite = { .suite_name = "Link Bonding mode 4 Unit Test Suite", .setup = test_setup, .teardown = testsuite_teardown, .unit_test_cases = { TEST_CASE_NAMED("test_mode4_lacp", test_mode4_lacp_wrapper), TEST_CASE_NAMED("test_mode4_rx", test_mode4_rx_wrapper), TEST_CASE_NAMED("test_mode4_tx_burst", test_mode4_tx_burst_wrapper), TEST_CASE_NAMED("test_mode4_marker", test_mode4_marker_wrapper), TEST_CASE_NAMED("test_mode4_expired", test_mode4_expired_wrapper), TEST_CASES_END() /**< NULL terminate unit test array */ } }; static int test_link_bonding_mode4(void) { return unit_test_suite_runner(&link_bonding_mode4_test_suite); } static struct test_command link_bonding_cmd = { .command = "link_bonding_mode4_autotest", .callback = test_link_bonding_mode4, }; REGISTER_TEST_COMMAND(link_bonding_cmd);