| /* |
| * Copyright (c) 2018 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if 1 |
| #define SYS_LOG_DOMAIN "tc-app" |
| #define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG |
| #define NET_LOG_ENABLED 1 |
| #endif |
| |
| #include <zephyr.h> |
| #include <errno.h> |
| |
| #include <net/net_core.h> |
| #include <net/net_l2.h> |
| #include <net/net_if.h> |
| #include <net/ethernet.h> |
| #include <net/net_context.h> |
| #include <net/net_app.h> |
| |
| #define MY_PORT 0 |
| #define PEER_PORT 4242 |
| |
| #define WAIT_TIME K_SECONDS(2) |
| #define CONNECT_TIME K_SECONDS(10) |
| |
| #if defined(CONFIG_NET_IPV6) |
| static struct net_app_ctx udp6[NET_TC_COUNT]; |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| static struct net_app_ctx udp4[NET_TC_COUNT]; |
| #endif |
| |
| static struct k_sem quit_lock; |
| |
| /* Generated by http://www.lipsum.com/ |
| * 3 paragraphs, 176 words, 1230 bytes of Lorem Ipsum |
| */ |
| const char lorem_ipsum[] = |
| "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " |
| "Vestibulum id cursus felis, sit amet suscipit velit. Integer " |
| "facilisis malesuada porta. Nunc at accumsan mauris. Etiam vehicula, " |
| "arcu consequat feugiat venenatis, tellus velit gravida ligula, quis " |
| "posuere sem leo eget urna. Curabitur condimentum leo nec orci " |
| "mattis, nec faucibus dui rutrum. Ut mollis orci in iaculis " |
| "consequat. Nulla volutpat nibh eu velit sagittis, a iaculis dui " |
| "aliquam." |
| "\n" |
| "Quisque interdum consequat eros a eleifend. Fusce dapibus nisl " |
| "sit amet velit posuere imperdiet. Quisque accumsan tempor massa " |
| "sit amet tincidunt. Integer sollicitudin vehicula tristique. Nulla " |
| "sagittis massa turpis, ac ultricies neque posuere eu. Nulla et " |
| "imperdiet ex. Etiam venenatis sed lacus tincidunt hendrerit. In " |
| "libero nisl, congue id tellus vitae, tincidunt tristique mauris. " |
| "Nullam sed porta massa. Sed condimentum sem eu convallis euismod. " |
| "Suspendisse lobortis purus faucibus, gravida turpis id, mattis " |
| "velit. Maecenas eleifend sapien eu tincidunt lobortis. Sed elementum " |
| "sapien id enim laoreet consequat." |
| "\n" |
| "Aenean et neque aliquam, lobortis lectus in, consequat leo. Sed " |
| "quis egestas nulla. Quisque ac risus quis elit mollis finibus. " |
| "Phasellus efficitur imperdiet metus." |
| "\n"; |
| |
| static int ipsum_len = sizeof(lorem_ipsum) - 1; |
| |
| struct stats { |
| u32_t sent; |
| u32_t received; |
| u32_t dropped; |
| u32_t wrong_order; |
| u32_t invalid; |
| |
| u32_t sent_time_sum; |
| u32_t sent_time_count; |
| s64_t sent_time; /* in milliseconds */ |
| }; |
| |
| struct configs; |
| |
| struct data { |
| /* Work controlling udp data sending */ |
| struct k_delayed_work recv; |
| struct net_app_ctx *udp; |
| struct configs *conf; |
| sa_family_t family; |
| |
| const char *proto; |
| u32_t expecting_udp; |
| u8_t priority; |
| |
| struct stats stats; |
| }; |
| |
| struct configs { |
| struct data ipv4[NET_TC_COUNT]; |
| struct data ipv6[NET_TC_COUNT]; |
| }; |
| |
| static struct configs conf = { |
| .ipv4 = { |
| [0 ... (NET_TC_COUNT - 1)] = { |
| .proto = "IPv4", |
| .family = AF_INET, |
| } |
| }, |
| .ipv6 = { |
| [0 ... (NET_TC_COUNT - 1)] = { |
| .proto = "IPv6", |
| .family = AF_INET6, |
| } |
| } |
| }; |
| |
| #define TYPE_SEQ_NUM 42 |
| |
| struct header { |
| u8_t type; |
| u8_t len; |
| union { |
| u8_t value[0]; |
| struct { |
| u32_t seq; |
| s64_t sent; |
| }; |
| }; |
| } __packed; |
| |
| #if CONFIG_NET_VLAN_COUNT > 1 |
| #define CREATE_MULTIPLE_TAGS |
| #endif |
| |
| struct ud { |
| struct net_if *first; |
| struct net_if *second; |
| }; |
| |
| #if defined(CREATE_MULTIPLE_TAGS) |
| static void iface_cb(struct net_if *iface, void *user_data) |
| { |
| struct ud *ud = user_data; |
| |
| if (net_if_l2(iface) != &NET_L2_GET_NAME(ETHERNET)) { |
| return; |
| } |
| |
| if (iface == ud->first) { |
| return; |
| } |
| |
| ud->second = iface; |
| } |
| #endif |
| |
| static int init_app(void) |
| { |
| struct net_if *iface; |
| int ret; |
| |
| #if defined(CREATE_MULTIPLE_TAGS) |
| struct net_if_addr *ifaddr; |
| struct in_addr addr4; |
| struct in6_addr addr6; |
| struct ud ud; |
| #endif |
| |
| iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET)); |
| if (!iface) { |
| NET_ERR("No ethernet interfaces found."); |
| return -ENOENT; |
| } |
| |
| #if defined(CONFIG_NET_VLAN) |
| ret = net_eth_vlan_enable(iface, CONFIG_SAMPLE_VLAN_TAG); |
| if (ret < 0) { |
| NET_ERR("Cannot enable VLAN for tag %d (%d)", |
| CONFIG_SAMPLE_VLAN_TAG, ret); |
| } |
| #endif |
| |
| #if defined(CREATE_MULTIPLE_TAGS) |
| ud.first = iface; |
| ud.second = NULL; |
| |
| net_if_foreach(iface_cb, &ud); |
| |
| /* This sample has two VLANs. For the second one we need to manually |
| * create IP address for this test. But first the VLAN needs to be |
| * added to the interface so that IPv6 DAD can work properly. |
| */ |
| ret = net_eth_vlan_enable(ud.second, CONFIG_SAMPLE_VLAN_TAG_2); |
| if (ret < 0) { |
| NET_ERR("Cannot enable VLAN for tag %d (%d)", |
| CONFIG_SAMPLE_VLAN_TAG_2, ret); |
| } |
| |
| #if defined(CONFIG_NET_IPV6) |
| if (net_addr_pton(AF_INET6, CONFIG_SAMPLE_IPV6_ADDR_2, &addr6)) { |
| NET_ERR("Invalid address: %s", CONFIG_SAMPLE_IPV6_ADDR_2); |
| return -EINVAL; |
| } |
| |
| ifaddr = net_if_ipv6_addr_add(ud.second, &addr6, NET_ADDR_MANUAL, 0); |
| if (!ifaddr) { |
| NET_ERR("Cannot add %s to interface %p", |
| CONFIG_SAMPLE_IPV6_ADDR_2, ud.second); |
| return -EINVAL; |
| } |
| #else |
| ARG_UNUSED(addr6); |
| #endif /* IPV6 */ |
| |
| #if defined(CONFIG_NET_IPV4) |
| if (net_addr_pton(AF_INET, CONFIG_SAMPLE_IPV4_ADDR_2, &addr4)) { |
| NET_ERR("Invalid address: %s", CONFIG_SAMPLE_IPV4_ADDR_2); |
| return -EINVAL; |
| } |
| |
| ifaddr = net_if_ipv4_addr_add(ud.second, &addr4, NET_ADDR_MANUAL, 0); |
| if (!ifaddr) { |
| NET_ERR("Cannot add %s to interface %p", |
| CONFIG_SAMPLE_IPV4_ADDR_2, ud.second); |
| return -EINVAL; |
| } |
| #else |
| ARG_UNUSED(addr4); |
| #endif /* IPV4 */ |
| |
| #endif |
| |
| return ret; |
| } |
| |
| static u32_t calc_time(u32_t count, u32_t sum) |
| { |
| if (!count) { |
| return 0; |
| } |
| |
| return (sum * 1000) / count; |
| } |
| |
| #define PRINT_STATISTICS_INTERVAL (30 * MSEC_PER_SEC) |
| |
| static void stats(struct data *data) |
| { |
| static bool first = true; |
| static s64_t next_print; |
| s64_t curr = k_uptime_get(); |
| |
| if (!next_print || (next_print < curr && |
| (!((curr - next_print) > PRINT_STATISTICS_INTERVAL)))) { |
| s64_t new_print; |
| int i; |
| |
| if (first) { |
| first = false; |
| goto skip_print; |
| } |
| |
| NET_INFO("Traffic class statistics:"); |
| NET_INFO(" Prio\tSent\tRecv\tDrop\tMiss\tTime (us)"); |
| |
| #if defined(CONFIG_NET_IPV6) |
| for (i = 0; i < NET_TC_COUNT; i++) { |
| u32_t round_trip_time = |
| calc_time( |
| data->conf->ipv6[i].stats.sent_time_count, |
| data->conf->ipv6[i].stats.sent_time_sum); |
| |
| NET_INFO("v6 %d\t%u\t%u\t%u\t%u\t%u", |
| data->conf->ipv6[i].priority, |
| data->conf->ipv6[i].stats.sent, |
| data->conf->ipv6[i].stats.received, |
| data->conf->ipv6[i].stats.dropped, |
| data->conf->ipv6[i].stats.wrong_order, |
| round_trip_time); |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV4) |
| for (i = 0; i < NET_TC_COUNT; i++) { |
| u32_t round_trip_time = |
| calc_time( |
| data->conf->ipv4[i].stats.sent_time_count, |
| data->conf->ipv4[i].stats.sent_time_sum); |
| |
| NET_INFO("v4 %d\t%u\t%u\t%u\t%u\t%u", |
| data->conf->ipv4[i].priority, |
| data->conf->ipv4[i].stats.sent, |
| data->conf->ipv4[i].stats.received, |
| data->conf->ipv4[i].stats.dropped, |
| data->conf->ipv4[i].stats.wrong_order, |
| round_trip_time); |
| } |
| #endif |
| NET_INFO("---"); |
| |
| skip_print: |
| new_print = curr + PRINT_STATISTICS_INTERVAL; |
| if (new_print > curr) { |
| next_print = new_print; |
| } else { |
| /* Overflow */ |
| next_print = PRINT_STATISTICS_INTERVAL - |
| (LLONG_MAX - curr); |
| } |
| } |
| } |
| |
| static struct net_pkt *prepare_send_pkt(struct net_app_ctx *ctx, |
| const char *name, |
| int *expecting_len, |
| struct data *data) |
| { |
| struct net_pkt *send_pkt; |
| struct header *hdr; |
| u32_t seq; |
| s32_t timeout = K_SECONDS(1); |
| |
| send_pkt = net_app_get_net_pkt(ctx, data->family, timeout); |
| if (!send_pkt) { |
| return NULL; |
| } |
| |
| seq = htonl(data->stats.sent + 1); |
| |
| *expecting_len = net_pkt_append(send_pkt, *expecting_len, |
| lorem_ipsum, timeout); |
| |
| hdr = (struct header *)send_pkt->frags->data; |
| hdr->type = TYPE_SEQ_NUM; |
| hdr->len = sizeof(seq); |
| |
| UNALIGNED_PUT(seq, &hdr->seq); |
| UNALIGNED_PUT(k_uptime_get(), &hdr->sent); |
| |
| return send_pkt; |
| } |
| |
| static bool send_udp_data(struct net_app_ctx *ctx, struct data *data) |
| { |
| s32_t timeout = K_SECONDS(1); |
| struct net_pkt *pkt; |
| size_t len; |
| int ret; |
| |
| data->expecting_udp = sys_rand32_get() % ipsum_len; |
| |
| pkt = prepare_send_pkt(ctx, data->proto, &data->expecting_udp, data); |
| if (!pkt) { |
| return false; |
| } |
| |
| len = net_pkt_get_len(pkt); |
| |
| NET_ASSERT_INFO(data->expecting_udp == len, |
| "Data to send %d bytes, real len %zu", |
| data->expecting_udp, len); |
| |
| data->stats.sent_time = k_uptime_get(); |
| |
| ret = net_app_send_pkt(ctx, pkt, NULL, 0, timeout, |
| UINT_TO_POINTER(len)); |
| if (ret < 0) { |
| net_pkt_unref(pkt); |
| } |
| |
| data->stats.sent++; |
| |
| k_delayed_work_submit(&data->recv, WAIT_TIME); |
| |
| stats(data); |
| |
| return true; |
| } |
| |
| static void send_more_data(struct net_app_ctx *ctx, struct data *data) |
| { |
| bool ret; |
| |
| do { |
| ret = send_udp_data(ctx, data); |
| if (!ret) { |
| /* Avoid too much flooding */ |
| k_sleep(K_MSEC(10)); |
| } |
| } while (!ret); |
| |
| /* We should not call k_yield() here as that will not let lower |
| * priority thread to run. |
| */ |
| k_sleep(K_MSEC(1)); |
| } |
| |
| static void udp_received(struct net_app_ctx *ctx, |
| struct net_pkt *pkt, |
| int status, |
| void *user_data) |
| { |
| struct data *data = ctx->user_data; |
| struct header *hdr = (struct header *)net_pkt_appdata(pkt); |
| |
| ARG_UNUSED(user_data); |
| ARG_UNUSED(status); |
| |
| if (data->expecting_udp != net_pkt_appdatalen(pkt)) { |
| NET_DBG("Sent %d bytes, received %u bytes", |
| data->expecting_udp, net_pkt_appdatalen(pkt)); |
| } |
| |
| net_pkt_unref(pkt); |
| |
| k_delayed_work_cancel(&data->recv); |
| |
| if (hdr->type != TYPE_SEQ_NUM) { |
| data->stats.invalid++; |
| } else { |
| if (ntohl(UNALIGNED_GET(&hdr->seq)) != data->stats.sent) { |
| data->stats.wrong_order++; |
| } else { |
| data->stats.received++; |
| |
| data->stats.sent_time_sum += k_uptime_get() - |
| data->stats.sent_time; |
| data->stats.sent_time_count++; |
| } |
| } |
| |
| send_more_data(ctx, data); |
| } |
| |
| static int connect_udp(sa_family_t family, struct net_app_ctx *ctx, |
| const char *peer, void *user_data, u8_t priority) |
| { |
| struct data *data = user_data; |
| size_t optlen = sizeof(priority); |
| int ret; |
| |
| data->udp = ctx; |
| |
| ret = net_app_init_udp_client(ctx, NULL, NULL, peer, PEER_PORT, |
| WAIT_TIME, user_data); |
| if (ret < 0) { |
| NET_ERR("Cannot init %s UDP client (%d)", data->proto, ret); |
| goto fail; |
| } |
| |
| #if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) |
| net_app_set_net_pkt_pool(ctx, tx_udp_slab, data_udp_pool); |
| #endif |
| |
| ret = net_app_set_cb(ctx, NULL, udp_received, NULL, NULL); |
| if (ret < 0) { |
| NET_ERR("Cannot set callbacks (%d)", ret); |
| goto fail; |
| } |
| |
| ret = net_app_connect(ctx, CONNECT_TIME); |
| if (ret < 0) { |
| NET_ERR("Cannot connect UDP (%d)", ret); |
| goto fail; |
| } |
| |
| #if defined(CONFIG_NET_IPV4) |
| if (family == AF_INET) { |
| net_context_set_option(ctx->ipv4.ctx, NET_OPT_PRIORITY, |
| &priority, sizeof(u8_t)); |
| |
| net_context_get_option(ctx->ipv4.ctx, NET_OPT_PRIORITY, |
| &priority, &optlen); |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV6) |
| if (family == AF_INET6) { |
| net_context_set_option(ctx->ipv6.ctx, NET_OPT_PRIORITY, |
| &priority, sizeof(u8_t)); |
| |
| net_context_get_option(ctx->ipv4.ctx, NET_OPT_PRIORITY, |
| &priority, &optlen); |
| } |
| #endif |
| |
| data->priority = priority; |
| fail: |
| return ret; |
| } |
| |
| static void wait_reply(struct k_work *work) |
| { |
| /* This means that we did not receive response in time. */ |
| struct data *data = CONTAINER_OF(work, struct data, recv); |
| |
| data->stats.dropped++; |
| |
| /* Send a new packet at this point */ |
| send_more_data(data->udp, data); |
| } |
| |
| static void setup_clients(void) |
| { |
| int ret, i; |
| |
| #if defined(CONFIG_NET_IPV6) |
| for (i = 0; i < NET_TC_COUNT; i++) { |
| k_delayed_work_init(&conf.ipv6[i].recv, wait_reply); |
| |
| conf.ipv6[i].conf = &conf; |
| |
| if (i % 2) { |
| NET_DBG("TC %d connecting to %s", i, |
| CONFIG_NET_APP_PEER_IPV6_ADDR); |
| ret = connect_udp(AF_INET6, &udp6[i], |
| CONFIG_NET_APP_PEER_IPV6_ADDR, |
| &conf.ipv6[i], i); |
| } else { |
| NET_DBG("TC %d connecting to %s", i, |
| CONFIG_SAMPLE_PEER_IPV6_ADDR_2); |
| ret = connect_udp(AF_INET6, &udp6[i], |
| CONFIG_SAMPLE_PEER_IPV6_ADDR_2, |
| &conf.ipv6[i], i); |
| } |
| |
| if (ret < 0) { |
| NET_ERR("Cannot init IPv6 UDP client %d (%d)", |
| i + 1, ret); |
| } |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_IPV4) |
| for (i = 0; i < NET_TC_COUNT; i++) { |
| k_delayed_work_init(&conf.ipv4[i].recv, wait_reply); |
| |
| conf.ipv4[i].conf = &conf; |
| |
| if (i % 2) { |
| NET_DBG("TC %d connecting to %s", i, |
| CONFIG_NET_APP_PEER_IPV4_ADDR); |
| ret = connect_udp(AF_INET, &udp4[i], |
| CONFIG_NET_APP_PEER_IPV4_ADDR, |
| &conf.ipv4[i], i); |
| } else { |
| NET_DBG("TC %d connecting to %s", i, |
| CONFIG_SAMPLE_PEER_IPV4_ADDR_2); |
| ret = connect_udp(AF_INET, &udp4[i], |
| CONFIG_SAMPLE_PEER_IPV4_ADDR_2, |
| &conf.ipv4[i], i); |
| } |
| |
| if (ret < 0) { |
| NET_ERR("Cannot init IPv4 UDP client %d (%d)", |
| i + 1, ret); |
| } |
| } |
| #endif |
| |
| /* We can start to send data when UDP is "connected" */ |
| for (i = 0; i < NET_TC_COUNT; i++) { |
| #if defined(CONFIG_NET_IPV6) |
| send_more_data(&udp6[i], &conf.ipv6[i]); |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| send_more_data(&udp4[i], &conf.ipv4[i]); |
| #endif |
| } |
| } |
| |
| void main(void) |
| { |
| k_sem_init(&quit_lock, 0, UINT_MAX); |
| |
| init_app(); |
| |
| /* This extra sleep is needed so that the network stabilizes a bit |
| * before we start to send data. This is important as we have multiple |
| * network interfaces and all of them should be configured properly |
| * before we continue. |
| */ |
| k_sleep(K_SECONDS(5)); |
| |
| setup_clients(); |
| |
| k_sem_take(&quit_lock, K_FOREVER); |
| } |