| /* |
| * Copyright (c) 2019 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(net_test, CONFIG_NET_SOCKETS_LOG_LEVEL); |
| |
| #include <stdio.h> |
| #include <zephyr/ztest_assert.h> |
| #include <zephyr/sys_clock.h> |
| #include <zephyr/net/net_ip.h> |
| #include <zephyr/net/socket.h> |
| #include <zephyr/net/socket_net_mgmt.h> |
| #include <zephyr/net/net_event.h> |
| #include <zephyr/net/ethernet_mgmt.h> |
| |
| #define MAX_BUF_LEN 64 |
| #define STACK_SIZE 1024 |
| #define THREAD_PRIORITY K_PRIO_COOP(8) |
| |
| static struct net_if *default_iface; |
| |
| 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) |
| { |
| const 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(const 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(const 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(const 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(const 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(const 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, NULL, |
| ð_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 = default_iface; |
| |
| 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 iface_cb(struct net_if *iface, void *user_data) |
| { |
| struct net_if **my_iface = user_data; |
| |
| if (net_if_l2(iface) == &NET_L2_GET_NAME(ETHERNET)) { |
| if (PART_OF_ARRAY(NET_IF_GET_NAME(eth_fake, 0), iface)) { |
| *my_iface = iface; |
| } |
| } |
| } |
| |
| static void test_net_mgmt_setup(void) |
| { |
| struct sockaddr_nm sockaddr; |
| int ret; |
| |
| net_if_foreach(iface_cb, &default_iface); |
| zassert_not_null(default_iface, "Cannot find test interface"); |
| |
| 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(default_iface); |
| 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; |
| } |
| } |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_net_mgmt_catch_kernel) |
| { |
| test_net_mgmt_catch_events(); |
| } |
| |
| ZTEST_USER(net_socket_net_mgmt, test_net_mgmt_catch_user) |
| { |
| test_net_mgmt_catch_events(); |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_net_mgmt_cleanup) |
| { |
| 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"); |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_ethernet_set_qav_kernel) |
| { |
| test_ethernet_set_qav(); |
| } |
| |
| ZTEST_USER(net_socket_net_mgmt, test_ethernet_set_qav_user) |
| { |
| 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"); |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_ethernet_get_qav_kernel) |
| { |
| test_ethernet_get_qav(); |
| } |
| |
| ZTEST_USER(net_socket_net_mgmt, test_ethernet_get_qav_user) |
| { |
| 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"); |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_ethernet_get_unknown_opt_kernel) |
| { |
| test_ethernet_get_unknown_option(); |
| } |
| |
| ZTEST_USER(net_socket_net_mgmt, test_ethernet_get_unknown_opt_user) |
| { |
| 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"); |
| } |
| |
| ZTEST(net_socket_net_mgmt, test_ethernet_set_unknown_opt_kernel) |
| { |
| test_ethernet_set_unknown_option(); |
| } |
| |
| ZTEST_USER(net_socket_net_mgmt, test_ethernet_set_unknown_opt_user) |
| { |
| test_ethernet_set_unknown_option(); |
| } |
| |
| static void *setup(void) |
| { |
| k_thread_system_pool_assign(k_current_get()); |
| test_net_mgmt_setup(); |
| return NULL; |
| } |
| |
| ZTEST_SUITE(net_socket_net_mgmt, NULL, setup, NULL, NULL, NULL); |