URL
https://opencores.org/ocsvn/xenie/xenie/trunk
Subversion Repositories xenie
[/] [xenie/] [trunk/] [examples/] [Eth_example/] [sw/] [XenieEthExample/] [WinSock_test/] [src/] [main.c] - Rev 4
Compare with Previous | Blame | View Log
/*************************************************************************** * * (C) Copyright 2017 DFC Design, s.r.o., Brno, Czech Republic * Author: Marek Kvas (m.kvas@dspfpga.com) * *************************************************************************** * * This file is part of Xenia Ethernet Example project. * * Xenia Ethernet Example project is free software: you can * redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Xenia Ethernet Example project is distributed in the hope that * it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Xenia Ethernet Example project. If not, * see <http://www.gnu.org/licenses/>. * *************************************************************************** * * !!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!! * This console application demonstrates functionality of Xenie Ethernet * Example design. The application generates high bandwidth network traffic * which - if accidentally routed to corporate/public network segment - can * saturate network infrastructure and effectively prevent network from * correct function. Some devices may even start to behave unpredictably * than. It is highly recommended to use this example on dedicated private * network segments only. If you are not sure about network infrastructure, * consult situation with your network administrator. * !!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!! * * As the first step, presence of any Xenie with correct running design * is verified using Network info discovery packet. This packet is * broadcasted to all interfaces. When Xenie receives it, it responds with * unicast response containing its MAC, IPv4 address and netmask. Because * Xenie example design doesn't have ARP, static ARP record is created * in order to enable unicast communication. * * !!! Static ARP record may - if not well understood and managed - * cause weird behaviour of the network. Static ARP record is not * permanent, so it disappears after system reboot, or ARP table reset. * * If Xenie is found on the network, number of transmitter / receiver * thread pairs is created. The number of this pairs is given by * SESSIONS_COUNT macro. * * Each transmitter thread sends UDP test traffic as fast as possible to * Xenie address. Xenie captures this packets, inserts some statistical * data into each datagram and sends them back. * This "looped back" datagrams are captured by the corresonding receiver * thread. It checks the statistics inserted by Xenie and determines whether * any datagrams were lost. The lost count and some other information is * stored in the context of each receiver. * * The main thread periodically reads the statistics from all the receiver * threads, sums them up (if applicable) and prints info to the console. * * Integrity of the received frames is protected by 32 bit CRC at the * Ethernet layer so it is not necessary to check for datagram payload * correctness. Corrupted packets are simply discarded by NIC. * * Timestamps from Xenie are used to measure bandwidth. Calculated number * should match number given by Windows task manager. Be careful about * interpretation of graphs shown by the task manager as it sums up TX and * RX traffic. So if 50 % of bandwidth is really used, graphs show 100 % * already. * * In order to generate maximum traffic, maximum frame size is used. * MTU macro can be modified to limit packet size. By default 9000 * bytes is used - it is standard maximum value for jumbo frames. * When this macro is set to value higher than real MTU used by * machine running this application, datagrams are fragmented at the IP * layer. As Xenie cannot handle fragmented IP packets, the test wont * work as expected - high but not 100 % packet loss is * most likely result. If host machine has MTU high enough, but * there is any network switch on the path to Xenie that doesn't * support set MTU, the traffic will most likely be lost completely. * !!! Check your NIC settings for the MTU before you run this application. * * */ #include <stdio.h> #include <stdlib.h> #include <winsock2.h> #include <WS2tcpip.h> #include <IPHlpApi.h> #include <stdint.h> /* Windows Socket library */ #pragma comment(lib,"ws2_32.lib") /* * Ports of services provided by xenie eth example design. * They are hardcoded in HDL. */ #define XENIE_TEST_PORT 0xdfc1 #define XENIE_NET_INFO_DICOVERY_PORT 0xdfcc /* Magic number hardcoded in HDL for our traffic identification */ #define TEST_PACKET_MAGIC 0xDFCDFC01 /* Namber of TX/RX thread pairs to be created */ #define SESSIONS_COUNT 60 /* * MTU must be set to value equal or less than MTU set on NIC. * 9000 is maximum for most of standard NICs. * * If set too high, Windows stack will fragment datagrams on IP * leyer. Xenie test design doesn't support fragmented packets - * communication will be corrupted. */ /* MTU of the NIC or path */ #define MTU 9000 /* Real length of Ethernet frame (pramble and FCS excluded)*/ #define MAX_ETH_FRAME_SIZE ((MTU) + 14) /* Max lenght of UDP payload to fulfill MTU */ #define MAX_PKT_UDP_DATA ((MAX_ETH_FRAME_SIZE) - (14 + 20 + 8)) /* Lenght of balast after out test protocol header to create maximum packet */ #define MAX_PKT_DATA_LENGTH ((MAX_PKT_UDP_DATA) - 32) /* Structure defining packet carrying Xenie's network settings */ #pragma pack(push) struct xenie_net_info_pkt_s { uint64_t tstmp; unsigned char mac_addr[6]; unsigned char pad[2]; IN_ADDR ip_addr; IN_ADDR net_mask; }; #pragma pack(pop) /* * Send xenie discovery packet. * Zero is returned on success and xenie argument is * filled with received data. */ int find_xenie(struct xenie_net_info_pkt_s *xenie) { int i; int res, ret = -1; SOCKET s; SOCKADDR_IN xenie_net_info_dicovery; DWORD optval; s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (s == INVALID_SOCKET) return -1; /* Enable broadcasts */ optval = 100; res = setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char *)&optval, sizeof(optval)); if (res) goto err; /* Set Timeout for response */ optval = 100; res = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *)&optval, sizeof(optval)); if (res) goto err; /* Send dicovery packet */ xenie_net_info_dicovery.sin_addr.s_addr = INADDR_BROADCAST; xenie_net_info_dicovery.sin_port = ntohs(XENIE_NET_INFO_DICOVERY_PORT); xenie_net_info_dicovery.sin_family = AF_INET; res = sendto(s," ", 1, 0, (SOCKADDR*)&xenie_net_info_dicovery, sizeof(xenie_net_info_dicovery)); if(res != 1) { printf("err %d\n", WSAGetLastError()); goto err; } /* Receive response */ res = recv(s, (char*)xenie, sizeof(*xenie), 0); if(res != sizeof(*xenie)) goto err; /* Change byteorder for IP addresses */ xenie->ip_addr.s_addr = ntohl(xenie->ip_addr.s_addr); xenie->net_mask.s_addr = ntohl(xenie->net_mask.s_addr); /* swap bytes for mac address */ for(i = 0; i < 3; i++) { unsigned char tmp; tmp = xenie->mac_addr[i]; xenie->mac_addr[i] = xenie->mac_addr[5-i]; xenie->mac_addr[5-i] = tmp; } ret = 0; err: closesocket(s); return ret; } /* * Beacause xenie with demo udp_ip stack doesn't suppor ARP. * it is necessary to create static ARP record if unicast * communication is needed. */ int create_arp_record(struct xenie_net_info_pkt_s *xenie) { unsigned int i; int res; int ret = 0; MIB_IPNETROW arp_entry; PMIB_IPNETTABLE ip_net_table; unsigned long arpTableLength = 0; /* * Get current ARP table to find whether adderess * is already assigned or not */ res = GetIpNetTable(NULL, &arpTableLength, 1); if (res != ERROR_INSUFFICIENT_BUFFER) return -1; ip_net_table = (PMIB_IPNETTABLE)malloc(arpTableLength); if (ip_net_table == NULL) return -1; res = GetIpNetTable(ip_net_table, &arpTableLength, 1); if (res) { ret = -1; goto end; } /* We have to create new record */ arp_entry.dwPhysAddrLen = 6; memcpy(&arp_entry.bPhysAddr, xenie->mac_addr, arp_entry.dwPhysAddrLen); res = GetBestInterface(xenie->ip_addr.s_addr, &arp_entry.dwIndex); if(res){ ret = -1; goto end; } arp_entry.dwAddr = xenie->ip_addr.s_addr; arp_entry.Type = MIB_IPNET_TYPE_STATIC; /* * First Search ARP table for our address whether * there is any similar record */ for (i = 0; i < ip_net_table->dwNumEntries; i++) { if (ip_net_table->table[i].dwAddr == xenie->ip_addr.s_addr) { if ((strncmp((char*)ip_net_table->table[i].bPhysAddr, (char*)xenie->mac_addr, 6) != 0) || (ip_net_table->table[i].Type != MIB_IPNET_TYPE_STATIC) || (ip_net_table->table[i].dwIndex != arp_entry.dwIndex)) { res = DeleteIpNetEntry(&ip_net_table->table[i]); } else { /* Record already exists so let it be */ goto end; } } } res = CreateIpNetEntry(&arp_entry); if(res) { ret = -1; goto end; } end: free(ip_net_table); return ret; } /* Structure definind packets used for testing */ struct test_counters { uint32_t loopback_pkt_cnt; uint32_t test_pkt_cnt; uint32_t unknown_port_pkt_cnt; uint32_t test_pkt_accepted_cnt; }; #pragma pack(push) struct testPkt { uint32_t magic; uint32_t seqNo; uint64_t tstmp; struct test_counters cnts; char data[MAX_PKT_DATA_LENGTH]; }; #pragma pack(pop) /* Structure used to managed threads */ struct threadManagement { HANDLE threadHandle; DWORD threadID; HANDLE exitEvent; }; /* Collection of statistics counters*/ struct rcv_stats { uint32_t received_pkts; uint32_t received_test_pkts; uint32_t lost_pkts; uint32_t seqNo_restarts; uint64_t max_pkt_period; }; /* Context for received thread */ struct rcvThreadData { struct threadManagement tmng; SOCKET *s; HANDLE accessMutex; uint32_t last_seqNo; uint64_t last_timestamp; uint32_t first_test_pkt_accepted_cnt; struct rcv_stats stats; struct test_counters cnts; unsigned int ring_low_watermark; }; /* Context for send thread */ struct sendThreadData { struct threadManagement tmng; HANDLE accessMutex; SOCKET *s; uint32_t sent_pkts; }; /* Structure keeping references to correspondind send/receive threads */ struct sendRcvSession { struct rcvThreadData rcv; struct sendThreadData snd; SOCKET s; }; /* Body od send threads */ DWORD WINAPI sendThread_func (LPVOID param) { int res; int i; struct sendThreadData *ctx = (struct sendThreadData*)param; struct testPkt pkt; ctx->sent_pkts = 0; pkt.magic = TEST_PACKET_MAGIC; /* Fill body of packet with random data */ srand( (unsigned)time( NULL ) ); for(i = 0; i < MAX_PKT_DATA_LENGTH; i++) { pkt.data[i] = rand(); } while(WaitForSingleObject(ctx->tmng.exitEvent,0) == WAIT_TIMEOUT) { WaitForSingleObject(ctx->accessMutex, INFINITE); pkt.seqNo = ctx->sent_pkts; res = send(*ctx->s, (char *)&pkt, MAX_PKT_UDP_DATA, 0); if (res < 0) { printf("ERROR: Send failed with error %d\n", WSAGetLastError()); } else { ctx->sent_pkts++; } ReleaseMutex(ctx->accessMutex); } } /* Budy of receive thread */ DWORD WINAPI rcvThread_func(LPVOID param) { int res; struct rcvThreadData *ctx = (struct rcvThreadData*)param; struct testPkt pkt; /* Loop until asked to exit */ while(WaitForSingleObject(ctx->tmng.exitEvent,0) == WAIT_TIMEOUT) { res = recv(*ctx->s, (char*)&pkt, sizeof(pkt), 0); if (res > 0) { /* packet received */ ctx->stats.received_pkts++; //pkt = (struct pkt*)cur_recv_req->pkt_addr; /* Check it is ours test packet */ if((pkt.magic == TEST_PACKET_MAGIC) && (res == MAX_PKT_UDP_DATA)) { WaitForSingleObject(ctx->accessMutex, INFINITE); /* Careful about the first packet */ if (ctx->stats.received_test_pkts) { if(pkt.seqNo < (ctx->last_seqNo+1)) { ctx->stats.seqNo_restarts++; } else { ctx->stats.lost_pkts += pkt.seqNo - ctx->last_seqNo -1; } } else { ctx->first_test_pkt_accepted_cnt = pkt.cnts.test_pkt_accepted_cnt; } ctx->stats.received_test_pkts++; ctx->last_seqNo = pkt.seqNo; ctx->cnts = pkt.cnts; ctx->last_timestamp = pkt.tstmp; ReleaseMutex(ctx->accessMutex); } else { /* This packet doesnt belong to our test */ printf("ERROR: Captured packet with wrong magic number or length\n"); } } else if (res == WSAETIMEDOUT) { // recv timed out - nothing happened continue; } else { // error occured printf("ERROR: recv returned error number %d\n", WSAGetLastError()); } } return 0; } /* Convenience function creating send/receive thread */ int startThread(LPTHREAD_START_ROUTINE lpStartAddress, struct threadManagement *tmng) { tmng->exitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (tmng->exitEvent == NULL) return -1; tmng->threadHandle = CreateThread(NULL, 0, lpStartAddress, tmng, 0, &tmng->threadID); if (tmng->threadHandle == NULL) { CloseHandle(tmng->threadHandle); return -1; } return 0; } /* Convenience function creating send/receive thread pair */ int create_session(struct sendRcvSession *session, struct xenie_net_info_pkt_s *xenie) { int res; SOCKADDR_IN xenie_addr; DWORD optval; /* Create socket common for both send and receive */ session->s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (session->s == INVALID_SOCKET) return -1; /* Set Timeout for response */ optval = 100; res = setsockopt(session->s, SOL_SOCKET, SO_RCVTIMEO, (char *)&optval, sizeof(optval)); if (res) { printf("ERROR: Cannot set timeout on socket\n"); goto clean_skt; } /* Set receive buffer for response */ optval = 32*1024*1024; res = setsockopt(session->s, SOL_SOCKET, SO_RCVBUF, (char *)&optval, sizeof(optval)); if (res) { printf("ERROR: Cannot set timeout on socket\n"); goto clean_skt; } /* Connect to test socket */ xenie_addr.sin_addr.s_addr = xenie->ip_addr.s_addr; xenie_addr.sin_port = ntohs(XENIE_TEST_PORT); xenie_addr.sin_family = AF_INET; res = connect(session->s, (SOCKADDR*)&xenie_addr, sizeof(xenie_addr)); if (res < 0) { printf("ERROR: Cannot send data to selected socket.\n"); goto clean_skt; } session->rcv.accessMutex = CreateMutex(NULL, 0, NULL); session->snd.accessMutex = CreateMutex(NULL, 0, NULL); if ((session->rcv.accessMutex == NULL) || (session->rcv.accessMutex == NULL)) { goto clean_mutex; } session->rcv.s = &session->s; session->snd.s = &session->s; /* Create thread for receiving */ if(startThread(rcvThread_func, &session->rcv.tmng)) { printf("ERROR: Cannot start receive thread.\n"); goto clean_mutex; } /* Create thread for sending */ if(startThread(sendThread_func, &session->snd.tmng)) { printf("ERROR: Cannot start send thread.\n"); goto clean_rcvThread; } return 0; clean_rcvThread: SetEvent(session->rcv.tmng.exitEvent); /* wait for thread to really end */ WaitForSingleObject(session->rcv.tmng.threadHandle, INFINITE); clean_mutex: CloseHandle(session->rcv.accessMutex); CloseHandle(session->snd.accessMutex); clean_skt: closesocket(session->s); return -1; } int main (int argc, char *argv[]) { WSADATA wsaData; int res; int i; struct xenie_net_info_pkt_s xenie; struct rcvThreadData rcvThreadCtx; struct sendThreadData sendThreadCtx; struct sendRcvSession sessions[SESSIONS_COUNT]; uint64_t last_timestamp = 0; uint32_t last_test_pkt_accepted_cnt =0; /* Windows sockets library init */ res = WSAStartup(MAKEWORD(2, 2), &wsaData); if (res != NO_ERROR) { printf("ERROR: Cannot initialize Windows Sockets library. Error: %d\n", WSAGetLastError()); return 1; } res = find_xenie(&xenie); if (res) { printf("ERROR: No xenie board has been found.\n"); return 1; } printf("Xenie board found:\n"); printf("MAC: %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", xenie.mac_addr[5],xenie.mac_addr[4], xenie.mac_addr[3], xenie.mac_addr[2], xenie.mac_addr[1], xenie.mac_addr[0]); printf("IP: %s\n", inet_ntoa(xenie.ip_addr)); printf("Net mask: %s\n\n", inet_ntoa(xenie.net_mask)); if (create_arp_record(&xenie)) { printf("ERROR: Cannot create static ARP record for Xenie borad\n"); return 1; } /* Clean memory with session contexts */ memset(sessions, 0, sizeof(sessions)); /* Start all sessions */ for (i = 0; i < SESSIONS_COUNT; i++) { printf(" Creating session %d ... ", i); if (create_session(&sessions[i], &xenie)) { printf("failed.\n"); return -1; } else { printf("Ok.\n"); } } /* Main thread prints statistics once a second */ while (1) { struct rcv_stats stats; struct test_counters cnts; uint32_t sent_pkts, received_pkts, lost_pkts; uint32_t test_pkt_cnt, test_pkt_accepted_cnt; uint32_t loopback_pkt_cnt, unknown_port_pkt_cnt; uint32_t pkt_cnt_per_unit; uint64_t tstmp, unit; double bandwidth; /* Collect statistics */ sent_pkts = 0; received_pkts = 0; lost_pkts = 0; test_pkt_cnt = 0; test_pkt_accepted_cnt = 0; loopback_pkt_cnt = 0; unknown_port_pkt_cnt = 0; for (i = 0; i < SESSIONS_COUNT; i++) { WaitForSingleObject(sessions[i].rcv.accessMutex, INFINITE); stats = sessions[i].rcv.stats; cnts = sessions[i].rcv.cnts; tstmp = sessions[i].rcv.last_timestamp; ReleaseMutex(sessions[i].rcv.accessMutex); WaitForSingleObject(sessions[i].snd.accessMutex, INFINITE); sent_pkts += sessions[i].snd.sent_pkts; ReleaseMutex(sessions[i].snd.accessMutex); received_pkts += stats.received_pkts; lost_pkts += stats.lost_pkts; } /* These are common for all sessions and don't sum them up*/ test_pkt_cnt = cnts.test_pkt_cnt; test_pkt_accepted_cnt = cnts.test_pkt_accepted_cnt; loopback_pkt_cnt = cnts.loopback_pkt_cnt; unknown_port_pkt_cnt = cnts.unknown_port_pkt_cnt; pkt_cnt_per_unit = test_pkt_accepted_cnt - last_test_pkt_accepted_cnt; last_test_pkt_accepted_cnt = test_pkt_accepted_cnt; unit = tstmp - last_timestamp; last_timestamp = tstmp; bandwidth = (double)pkt_cnt_per_unit * MAX_ETH_FRAME_SIZE/((double)unit/156250000); printf("----------\n"); printf("Bandwidth %.3f MB/s (%.1f %%)\n", bandwidth/1024/1024, 100*8*bandwidth/10000000000); printf("Sent pkts %-9u, received %-9u, lost %-5u (%-5d)\n", sent_pkts, received_pkts, lost_pkts, test_pkt_accepted_cnt - sessions[0].rcv.first_test_pkt_accepted_cnt - sent_pkts); printf("test_pkt_cnt %-9u, test_accepted_pkt_cnt, " "%-9u loopback %-9u unknown_pkt_cnt %-9u\n", test_pkt_cnt, test_pkt_accepted_cnt, loopback_pkt_cnt, unknown_port_pkt_cnt); Sleep(1000); } return 0; }