/* * Copyright (c) 2019 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); #include #include #include #include #include #include #include #include #define MAX_BUF_LEN 64 #define STACK_SIZE 1024 #define THREAD_PRIORITY K_PRIO_COOP(8) static ZTEST_BMEM int fd; static ZTEST_BMEM struct in6_addr addr_v6; static ZTEST_DMEM struct in_addr addr_v4 = { { { 192, 0, 2, 3 } } }; #if IS_ENABLED(CONFIG_NET_SOCKETS_LOG_LEVEL_DBG) #define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) #else #define DBG(fmt, ...) #endif static const uint8_t mac_addr_init[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; struct eth_fake_context { struct net_if *iface; uint8_t mac_address[6]; bool auto_negotiation; bool full_duplex; bool link_10bt; bool link_100bt; bool promisc_mode; struct { bool qav_enabled; int idle_slope; int delta_bandwidth; } priority_queues[2]; }; static struct eth_fake_context eth_fake_data; static void eth_fake_iface_init(struct net_if *iface) { struct device *dev = net_if_get_device(iface); struct eth_fake_context *ctx = dev->data; ctx->iface = iface; net_if_set_link_addr(iface, ctx->mac_address, sizeof(ctx->mac_address), NET_LINK_ETHERNET); ethernet_init(iface); } static int eth_fake_send(struct device *dev, struct net_pkt *pkt) { ARG_UNUSED(dev); ARG_UNUSED(pkt); return 0; } static int eth_fake_get_total_bandwidth(struct eth_fake_context *ctx) { if (ctx->link_100bt) { return 100 * 1000 * 1000 / 8; } if (ctx->link_10bt) { return 10 * 1000 * 1000 / 8; } /* No link */ return 0; } static void eth_fake_recalc_qav_delta_bandwidth(struct eth_fake_context *ctx) { int bw; int i; bw = eth_fake_get_total_bandwidth(ctx); for (i = 0; i < ARRAY_SIZE(ctx->priority_queues); ++i) { if (bw == 0) { ctx->priority_queues[i].delta_bandwidth = 0; } else { ctx->priority_queues[i].delta_bandwidth = (ctx->priority_queues[i].idle_slope * 100); ctx->priority_queues[i].delta_bandwidth /= bw; } } } static void eth_fake_recalc_qav_idle_slopes(struct eth_fake_context *ctx) { int bw; int i; bw = eth_fake_get_total_bandwidth(ctx); for (i = 0; i < ARRAY_SIZE(ctx->priority_queues); ++i) { ctx->priority_queues[i].idle_slope = (ctx->priority_queues[i].delta_bandwidth * bw) / 100; } } static int eth_fake_set_config(struct device *dev, enum ethernet_config_type type, const struct ethernet_config *config) { struct eth_fake_context *ctx = dev->data; int priority_queues_num = ARRAY_SIZE(ctx->priority_queues); enum ethernet_qav_param_type qav_param_type; int queue_id; switch (type) { case ETHERNET_CONFIG_TYPE_QAV_PARAM: queue_id = config->qav_param.queue_id; qav_param_type = config->qav_param.type; if (queue_id < 0 || queue_id >= priority_queues_num) { return -EINVAL; } switch (qav_param_type) { case ETHERNET_QAV_PARAM_TYPE_STATUS: ctx->priority_queues[queue_id].qav_enabled = config->qav_param.enabled; break; case ETHERNET_QAV_PARAM_TYPE_IDLE_SLOPE: ctx->priority_queues[queue_id].idle_slope = config->qav_param.idle_slope; eth_fake_recalc_qav_delta_bandwidth(ctx); break; case ETHERNET_QAV_PARAM_TYPE_DELTA_BANDWIDTH: ctx->priority_queues[queue_id].delta_bandwidth = config->qav_param.delta_bandwidth; eth_fake_recalc_qav_idle_slopes(ctx); break; default: return -ENOTSUP; } break; default: return -ENOTSUP; } return 0; } static int eth_fake_get_config(struct device *dev, enum ethernet_config_type type, struct ethernet_config *config) { struct eth_fake_context *ctx = dev->data; int priority_queues_num = ARRAY_SIZE(ctx->priority_queues); enum ethernet_qav_param_type qav_param_type; int queue_id; switch (type) { case ETHERNET_CONFIG_TYPE_QAV_PARAM: queue_id = config->qav_param.queue_id; qav_param_type = config->qav_param.type; if (queue_id < 0 || queue_id >= priority_queues_num) { return -EINVAL; } switch (qav_param_type) { case ETHERNET_QAV_PARAM_TYPE_STATUS: config->qav_param.enabled = ctx->priority_queues[queue_id].qav_enabled; break; case ETHERNET_QAV_PARAM_TYPE_IDLE_SLOPE: case ETHERNET_QAV_PARAM_TYPE_OPER_IDLE_SLOPE: /* No distinction between idle slopes for fake eth */ config->qav_param.idle_slope = ctx->priority_queues[queue_id].idle_slope; break; case ETHERNET_QAV_PARAM_TYPE_DELTA_BANDWIDTH: config->qav_param.delta_bandwidth = ctx->priority_queues[queue_id].delta_bandwidth; break; case ETHERNET_QAV_PARAM_TYPE_TRAFFIC_CLASS: /* Default TC for BE - it doesn't really matter here */ config->qav_param.traffic_class = net_tx_priority2tc(NET_PRIORITY_BE); break; default: return -ENOTSUP; } break; default: return -ENOTSUP; } return 0; } static enum ethernet_hw_caps eth_fake_get_capabilities(struct device *dev) { return ETHERNET_AUTO_NEGOTIATION_SET | ETHERNET_LINK_10BASE_T | ETHERNET_LINK_100BASE_T | ETHERNET_DUPLEX_SET | ETHERNET_QAV | ETHERNET_PROMISC_MODE | ETHERNET_PRIORITY_QUEUES; } static struct ethernet_api eth_fake_api_funcs = { .iface_api.init = eth_fake_iface_init, .get_capabilities = eth_fake_get_capabilities, .set_config = eth_fake_set_config, .get_config = eth_fake_get_config, .send = eth_fake_send, }; static int eth_fake_init(struct device *dev) { struct eth_fake_context *ctx = dev->data; int i; ctx->auto_negotiation = true; ctx->full_duplex = true; ctx->link_10bt = true; ctx->link_100bt = false; memcpy(ctx->mac_address, mac_addr_init, 6); /* Initialize priority queues */ for (i = 0; i < ARRAY_SIZE(ctx->priority_queues); ++i) { ctx->priority_queues[i].qav_enabled = true; if (i + 1 == ARRAY_SIZE(ctx->priority_queues)) { /* 75% for the last priority queue */ ctx->priority_queues[i].delta_bandwidth = 75; } else { /* 0% for the rest */ ctx->priority_queues[i].delta_bandwidth = 0; } } eth_fake_recalc_qav_idle_slopes(ctx); return 0; } ETH_NET_DEVICE_INIT(eth_fake, "eth_fake", eth_fake_init, device_pm_control_nop, ð_fake_data, NULL, CONFIG_ETH_INIT_PRIORITY, ð_fake_api_funcs, NET_ETH_MTU); /* A test thread that spits out events that we can catch and show to user */ static void trigger_events(void) { int operation = 0; struct net_if_addr *ifaddr_v6, *ifaddr_v4; struct net_if *iface; int ret; iface = net_if_get_default(); net_ipv6_addr_create(&addr_v6, 0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0003); while (1) { switch (operation) { case 0: ifaddr_v6 = net_if_ipv6_addr_add(iface, &addr_v6, NET_ADDR_MANUAL, 0); if (!ifaddr_v6) { LOG_ERR("Cannot add IPv%c address", '6'); break; } break; case 1: ifaddr_v4 = net_if_ipv4_addr_add(iface, &addr_v4, NET_ADDR_MANUAL, 0); if (!ifaddr_v4) { LOG_ERR("Cannot add IPv%c address", '4'); break; } break; case 2: ret = net_if_ipv6_addr_rm(iface, &addr_v6); if (!ret) { LOG_ERR("Cannot del IPv%c address", '6'); break; } break; case 3: ret = net_if_ipv4_addr_rm(iface, &addr_v4); if (!ret) { LOG_ERR("Cannot del IPv%c address", '4'); break; } break; default: operation = -1; break; } operation++; k_sleep(K_MSEC(100)); } } K_THREAD_DEFINE(trigger_events_thread_id, STACK_SIZE, trigger_events, NULL, NULL, NULL, THREAD_PRIORITY, 0, -1); static char *get_ip_addr(char *ipaddr, size_t len, sa_family_t family, struct net_mgmt_msghdr *hdr) { char *buf; buf = net_addr_ntop(family, hdr->nm_msg, ipaddr, len); if (!buf) { return "?"; } return buf; } static void test_net_mgmt_setup(void) { struct sockaddr_nm sockaddr; int ret; fd = socket(AF_NET_MGMT, SOCK_DGRAM, NET_MGMT_EVENT_PROTO); zassert_false(fd < 0, "Cannot create net_mgmt socket (%d)", errno); #ifdef CONFIG_USERSPACE /* Set the underlying net_context to global access scope so that * other scenario threads may use it */ void *ctx = zsock_get_context_object(fd); zassert_not_null(ctx, "null net_context"); k_object_access_all_grant(ctx); #endif /* CONFIG_USERSPACE */ memset(&sockaddr, 0, sizeof(sockaddr)); sockaddr.nm_family = AF_NET_MGMT; sockaddr.nm_ifindex = net_if_get_by_iface(net_if_get_default()); sockaddr.nm_pid = (uintptr_t)k_current_get(); sockaddr.nm_mask = NET_EVENT_IPV6_DAD_SUCCEED | NET_EVENT_IPV6_ADDR_ADD | NET_EVENT_IPV6_ADDR_DEL; ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); zassert_false(ret < 0, "Cannot bind net_mgmt socket (%d)", errno); k_thread_start(trigger_events_thread_id); } static void test_net_mgmt_catch_events(void) { struct sockaddr_nm event_addr; socklen_t event_addr_len; char ipaddr[INET6_ADDRSTRLEN]; uint8_t buf[MAX_BUF_LEN]; int event_count = 2; int ret; while (event_count > 0) { struct net_mgmt_msghdr *hdr; memset(buf, 0, sizeof(buf)); event_addr_len = sizeof(event_addr); ret = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&event_addr, &event_addr_len); if (ret < 0) { continue; } hdr = (struct net_mgmt_msghdr *)buf; if (hdr->nm_msg_version != NET_MGMT_SOCKET_VERSION_1) { /* Do not know how to parse the message */ continue; } switch (event_addr.nm_mask) { case NET_EVENT_IPV6_ADDR_ADD: DBG("IPv6 address added to interface %d (%s)\n", event_addr.nm_ifindex, get_ip_addr(ipaddr, sizeof(ipaddr), AF_INET6, hdr)); zassert_equal(strncmp(ipaddr, "2001:db8::3", sizeof(ipaddr) - 1), 0, "Invalid IPv6 address %s added", ipaddr); event_count--; break; case NET_EVENT_IPV6_ADDR_DEL: DBG("IPv6 address removed from interface %d (%s)\n", event_addr.nm_ifindex, get_ip_addr(ipaddr, sizeof(ipaddr), AF_INET6, hdr)); zassert_equal(strncmp(ipaddr, "2001:db8::3", sizeof(ipaddr) - 1), 0, "Invalid IPv6 address %s removed", ipaddr); event_count--; break; } } } static void test_net_mgmt_catch_kernel(void) { test_net_mgmt_catch_events(); } static void test_net_mgmt_catch_user(void) { test_net_mgmt_catch_events(); } static void test_net_mgmt_cleanup(void) { k_thread_abort(trigger_events_thread_id); } static void test_ethernet_set_qav(void) { struct ethernet_req_params params; int ret; memset(¶ms, 0, sizeof(params)); params.qav_param.queue_id = 1; params.qav_param.type = ETHERNET_QAV_PARAM_TYPE_STATUS; params.qav_param.enabled = true; ret = setsockopt(fd, SOL_NET_MGMT_RAW, NET_REQUEST_ETHERNET_SET_QAV_PARAM, ¶ms, sizeof(params)); zassert_equal(ret, 0, "Cannot set Qav parameters"); } static void test_ethernet_set_qav_kernel(void) { test_ethernet_set_qav(); } static void test_ethernet_set_qav_user(void) { test_ethernet_set_qav(); } static void test_ethernet_get_qav(void) { struct ethernet_req_params params; socklen_t optlen = sizeof(params); int ret; memset(¶ms, 0, sizeof(params)); params.qav_param.queue_id = 1; params.qav_param.type = ETHERNET_QAV_PARAM_TYPE_STATUS; ret = getsockopt(fd, SOL_NET_MGMT_RAW, NET_REQUEST_ETHERNET_GET_QAV_PARAM, ¶ms, &optlen); zassert_equal(ret, 0, "Cannot get Qav parameters (%d)", ret); zassert_equal(optlen, sizeof(params), "Invalid optlen (%d)", optlen); zassert_true(params.qav_param.enabled, "Qav not enabled"); } static void test_ethernet_get_qav_kernel(void) { test_ethernet_get_qav(); } static void test_ethernet_get_qav_user(void) { test_ethernet_get_qav(); } static void test_ethernet_get_unknown_option(void) { struct ethernet_req_params params; socklen_t optlen = sizeof(params); int ret; memset(¶ms, 0, sizeof(params)); ret = getsockopt(fd, SOL_NET_MGMT_RAW, NET_REQUEST_ETHERNET_GET_PRIORITY_QUEUES_NUM, ¶ms, &optlen); zassert_equal(ret, -1, "Could get prio queue parameters (%d)", errno); zassert_equal(errno, EINVAL, "prio queue get parameters"); } static void test_ethernet_get_unknown_opt_kernel(void) { test_ethernet_get_unknown_option(); } static void test_ethernet_get_unknown_opt_user(void) { test_ethernet_get_unknown_option(); } static void test_ethernet_set_unknown_option(void) { struct ethernet_req_params params; socklen_t optlen = sizeof(params); int ret; memset(¶ms, 0, sizeof(params)); ret = setsockopt(fd, SOL_NET_MGMT_RAW, NET_REQUEST_ETHERNET_SET_MAC_ADDRESS, ¶ms, optlen); zassert_equal(ret, -1, "Could set promisc_mode parameters (%d)", errno); zassert_equal(errno, EINVAL, "promisc_mode set parameters"); } static void test_ethernet_set_unknown_opt_kernel(void) { test_ethernet_set_unknown_option(); } static void test_ethernet_set_unknown_opt_user(void) { test_ethernet_set_unknown_option(); } void test_main(void) { k_thread_system_pool_assign(k_current_get()); ztest_test_suite(socket_net_mgmt, ztest_unit_test(test_net_mgmt_setup), ztest_unit_test(test_net_mgmt_catch_kernel), ztest_user_unit_test(test_net_mgmt_catch_user), ztest_unit_test(test_net_mgmt_cleanup), ztest_unit_test(test_ethernet_set_qav_kernel), ztest_user_unit_test(test_ethernet_set_qav_user), ztest_unit_test(test_ethernet_get_qav_kernel), ztest_user_unit_test(test_ethernet_get_qav_user), ztest_unit_test(test_ethernet_get_unknown_opt_kernel), ztest_user_unit_test( test_ethernet_get_unknown_opt_user), ztest_unit_test(test_ethernet_set_unknown_opt_kernel), ztest_user_unit_test( test_ethernet_set_unknown_opt_user)); ztest_run_test_suite(socket_net_mgmt); }