OpenCores
URL https://opencores.org/ocsvn/xenie/xenie/trunk

Subversion Repositories xenie

[/] [xenie/] [trunk/] [examples/] [Eth_example/] [sw/] [XenieEthExample/] [WinSock_test/] [src/] [main.c] - Rev 13

Go to most recent revision | 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;
}
 

Go to most recent revision | Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.