TCP/IP Stack Support for eCos The Common Networking for eCos package provides support for a complete TCP/IP networking stack. The design allows for the actual stack to be modular and at the current time two different implementations, one based on OpenBSD from 2000 and a new version based on FreeBSD, are available. The particulars of each stack implementation are presented in separate sections following this top-level discussion. Ethernet Driver Design Currently, the networking stack only supports ethernet based networking. The network drivers use a two-layer design. One layer is hardware independent and contains all the stack specific code. The other layer is platform dependent and communicates with the hardware independent layer via a very simple API. In this way, hardware device drivers can actually be used with other stacks, if the same API can be provided by that stack. We designed the drivers this way to encourage the development of other stacks in eCos while allowing re-use of the actual hardware specific code. More comprehensive documentation of the ethernet device driver and the associated API can be found in the generic ethernet device driver documentation The driver and API is the same as the minimal debug stack used by the RedBoot application. See the RedBoot documentation for further information. Sample Code Many examples using the networking support are provided. These are arranged as eCos test programs, primarily for use in verifying the package, but they can also serve as useful frameworks for program design. We have taken a KISS approach to building programs which use the network. A single include file <network.h> is all that is required to access the stack. A complete, annotated test program can be found at net/common/VERSION/tests/ftp_test.c, with its associated files. Configuring IP Addresses Each interface (“eth0” and “eth1”) has independent configuration of its setup. Each can be set up manually (in which case you must write code to do this), or by using BOOTP/DHCP, or explicitly, with configured values. If additional interfaces are added, these must be configured manually. The configurable values are: IP address netmask broadcast address gateway/router server address. Server address is the DHCP server if applicable, but in addition, many test cases use it as “the machine to talk to” in whatever manner the test exercises the protocol stack. The initialization is invoked by calling the C routine void init_all_network_interfaces(void); Additionally, if the system is configured to support IPv6 then each interface may have an address assigned which is a composite of a 64 bit prefix and the 32 bit IPv4 address for that interface. The prefix is controlled by the CDL setting CYGHWR_NET_DRIVER_ETH0_IPV6_PREFIX for “eth0”, etc. This is a CDL booldata type, allowing this address to be suppressed if not desired. Alternatively, the system can configure its IPv6 address using router solicitation. When the CDL option CYGOPT_NET_IPV6_ROUTING_THREAD is enabled, init_all_network_interface will start a thread which sends out router solicit messages, process router advertisements and thus configure an IPv6 address to the interface. Refer to the test cases, …/packages/net/common/VERSION/tests/ftp_test.c for example usage, and the source files in …/packages/net/common/VERSION/src/bootp_support.c and network_support.c to see what that call does. This assumes that the MAC address (also known as ESA or Ethernet Station Address) is already defined in the serial EEPROM or however the particular target implements this; support for setting the MAC address is hardware dependent. DHCP support is active by default, and there are configuration options to control it. Firstly, in the top level of the “Networking” configuration tree, “Use full DHCP instead of BOOTP” enables DHCP, and it contains an option to have the system provide a thread to renew DHCP leases and manage lease expiry. Secondly, the individual interfaces “eth0” and “eth1” each have new options within the “Use BOOTP/DHCP to initialize ‘ethX’” to select whether to use DHCP rather than BOOTP. Note that you are completely at liberty to ignore this startup code and its configuration in building your application. init_all_network_interfaces() is provided for three main purposes: For use by Red Hat's own test programs. As an easy “get you going” utility for newcomers to eCos. As readable example code from which further development might start. If your application has different requirements for bringing up available network interfaces, setting up routes, determining IP addresses and the like from the defaults that the example code provides, you can write your own initialization code to use whatever sequence of ioctl() function calls carries out the desired setup. Analogously, in larger systems, a sequence of “ifconfig&rdquo invocations is used; these mostly map to ioctl() calls to manipulate the state of the interface in question. Tests and Demonstrations <!-- <xref> -->Loopback tests By default, only tests which can execute on any target will be built. These therefore do not actually use external network interfaces (though they may configure and initialize them) but are limited to testing via the loopback interface. ping_lo_test - ping test of the loopback address tcp_lo_select - simple test of select with TCP via loopback tcp_lo_test - trivial TCP test via loopback udp_lo_test - trivial UDP test via loopback multi_lo_select - test of multiple select() calls simultaneously Building the Network Tests To build further network tests, ensure that the configuration option CYGPKG_NET_BUILD_TESTS is set in your build and then make the tests in the usual way. Alternatively (with that option set) use make -C net/common/VERSION/ tests after building the eCos library, if you wish to build only the network tests. This should give test executables in install/tests/net/common/VERSION/tests including the following: socket_test - trivial test of socket creation API mbuf_test - trivial test of mbuf allocation API ftp_test - simple FTP test, connects to “server” ping_test - pings “server” and non-existent host to test timeout dhcp_test - ping test, but also relinquishes and reacquires DHCP leases periodically flood - a flood ping test; use with care tcp_echo - data forwarding program for performance test nc_test_master - network characterization master nc_test_slave - network characterization slave server_test - a very simple server example tftp_client_test - performs a tftp get and put from/to “server” tftp_server_test - runs a tftp server for a short while set_mac_address - set MAC address(es) of interfaces in NVRAM bridge - contributed network bridge code nc6_test_master - IPv4/IPv6 network characterization master nc6_test_slave - IPv4/IPv6 network characterization slave ga_server_test - a very simple IPv4/IPv6 server example Standalone Tests socket_test - trivial test of socket creation API mbuf_test - trivial test of mbuf allocation API These two do not communicate over the net; they just perform simple API tests then exit. ftp_test - simple FTP test, connects to “server” This test initializes the interface(s) then connects to the FTP server on the “server” machine for for each active interface in turn, confirms that the connection was successful, disconnects and exits. This tests interworking with the server. ping_test - pings “server” and non-existent host to test timeout This test initializes the interface(s) then pings the server machine in the standard way, then pings address “32 up” from the server in the expectation that there is no machine there. This confirms that the successful ping is not a false positive, and tests the receive timeout. If there is such a machine, of course the 2nd set of pings succeeds, confirming that we can talk to a machine not previously mentioned by configuration or by bootp. It then does the same thing on the other interface, eth1. If IPv6 is enabled, the program will also ping to the address it last received a router advertisement from. Also a ping will be made to that address plus 32, in a similar way the the IPv4 case. dhcp_test - ping test, but also manipulates DHCP leases This test is very similar to the ping test, but in addition, provided the network package is not configured to do this automatically, it manually relinquishes and reclaims DHCP leases for all available interfaces. This tests the external API to DHCP. See section below describing this. flood - a flood ping test; use with care This test performs pings on all interfaces as quickly as possible, and only prints status information periodically. Flood pinging is bad for network performance; so do not use this test on general purpose networks unless protected by a switch. Performance Test tcp_echo - data forwarding program for performance test tcp_echo is one part of the standard performance test we use. The other parts are host programs tcp_source and tcp_sink. To make these (under your HOST system) cd to the tests source directory in the eCos repository and type “make -f make.host” - this should build tcp_source and tcp_sink. The host program “tcp_source” sends data to the target. On the target, “tcp_echo” sends it onwards to “tcp_sink” running on your host. So the target must receive and send on all the data that tcp_source sends it; the time taken for this is measured and the data rate is calculated. To invoke the test, first start tcp_echo on the target board and wait for it to become quiescent - it will report work to calibrate a CPU load which can be used to simulate real operating conditions for the stack. Then on your host machine, in one terminal window, invoke tcp_sink giving it the IP address (or hostname) of one interface of the target board. For example “tcp_sink 10.130.39.66”. tcp_echo on the target will print something like “SINK connection from 10.130.39.13:1143” when tcp_sink is correctly invoked. Next, in another host terminal window, invoke tcp_source, giving it the IP address (or hostname) of an interface of the target board, and optionally a background load to apply to the target while the test runs. For example, “tcp_source 194.130.39.66” to run the test with no additional target CPU load, or “tcp_source 194.130.39.66 85” to load it up to 85% used. The target load must be a multiple of 5. tcp_echo on the target will print something like “SOURCE connection from 194.130.39.13:1144” when tcp_source is correctly invoked. You can connect tcp_sink to one target interface and tcp_source to another, or both to the same interface. Similarly, you can run tcp_sink and tcp_source on the same host machine or different ones. TCP/IP and ARP look after them finding one another, as intended. nc_test_master - network characterization master nc_test_slave - network characterization slave These tests talk to each other to measure network performance. They can each run on either a test target or a host computer given some customization to your local environment. As provided, nc_test_slave must run on the test target, and nc_test_master must be run on a host computer, and be given the test target's IP address or hostname. The tests print network performance for various packet sizes over UDP and TCP, versus various additional CPU loads on the target. The programs nc6_test_slave nc6_test_master are additional forms which support both IPv4 and IPv6 addressing. Interactive Tests server_test - a very simple server example This test simply awaits a connection on port 7734 and after accepting a connection, gets a packet (with a timeout of a few seconds) and prints it. The connection is then closed. We then loop to await the next connection, and so on. To use it, telnet to the target on port 7734 then type something (quickly!) % telnet 172.16.19.171 7734 Hello target board and the test program will print something like: connection from 172.16.19.13:3369 buf = "Hello target board" ga_server_test - another very simple server example This is a variation on the ga_server_test test with the difference being that it uses the getaddrinfo function to set up its addresses. On a system with IPv6 enabled, it will listen on port 7734 for a TCP connection via either IPv4 or IPv6. tftp_client_test - performs a tftp get and put from/to “server” This is only partially interactive. You need to set things up on the “server” in order for this to work, and you will need to look at the server afterwards to confirm that all was well. For each interface in turn, this test attempts to read by tftp from the server, a file called tftp_get and prints the status and contents it read (if any). It then writes the same data to a file called tftp_put on the same server. In order for this to succeed, both files must already exist. The TFTP protocol does not require that a WRQ request _create_ a file, just that it can write it. The TFTP server on Linux certainly will only allow writes to an existing file, given the appropriate permission. Thus, you need to have these files in place, with proper permission, before running the test. The conventional place for the tftp server to operate in LINUX is /tftpboot/; you will likely need root privileges to create files there. The data contents of tftp_get can be anything you like, but anything very large will waste lots of time printing it on the test’s stdout, and anything above 32kB will cause a buffer overflow and unpredictable failure. Creating an empty tftp_put file (eg. by copying /dev/null to it) is neatest. So before the test you should have something like: -rw-rw-rw- 1 root 1076 May 1 11:39 tftp_get -rw-rw-rw- 1 root 0 May 1 15:52 tftp_put note that both files have public permissions wide open. After running the test, tftp_put should be a copy of tftp_get. -rw-rw-rw- 1 root 1076 May 1 11:39 tftp_get -rw-rw-rw- 1 root 1076 May 1 15:52 tftp_put If the configuration contains IPv6 support, the test program will also use IPv6. It will attempt to put/get the files listed above from the address it last received a routers solicit from. tftp_server_test - runs a tftp server for a short while This test is truly interactive, in that you can use a standard tftp application to get and put files from the server, during the 5 minutes that it runs. The dummy filesystem which underlies the server initially contains one file, called “uu” which contains part of a familiar text and some padding. It also accommodates creation of 3 further files of up to 1Mb in size and names of up to 256 bytes. Exceeding these limits will cause a buffer overflow and unpredictable failure. The dummy filesystem is an implementation of the generic API which allows a true filesystem to be attached to the tftp server in the network stack. We have been testing the tftp server by running the test on the target board, then using two different host computers connecting to the different target interfaces, putting a file from each, getting the “uu” file, and getting the file from the other computer. This verifies that data is preserved during the transfer as well as interworking with standard tftp applications. Maintenance Tools set_mac_address - set MAC address(es) of interfaces in NVRAM This program makes an example ioctl() call SIOCSIFHWADDR “Socket IO Set InterFace HardWare ADDRess” to set the MAC address on targets where this is supported and enabled in the configuration. You must edit the source to choose a MAC address and further edit it to allow this very dangerous operation. Not all ethernet drivers support this operation, because most ethernet hardware does not support it — or it comes pre-set from the factory. Do not use this program. Support Features TFTP The TFTP client and server are described in tftp_support.h; The TFTP client has and new and an older, deprecated, API. The new API works for both IPv4 and IPv6 where as the deprecated API is IPv4 only. The new API is as follows: int tftp_client_get(const char * const filename, const char * const server, const int port, char *buf, int len, const int mode, int * const err); int tftp_client_put(const char * const filename, const char * const server, const int port, const char *buf, int len, const int mode, int *const err); Currently server can only be a numeric IPv4 or IPv6 address. The resolver is currently not used, but it is planned to add this feature (patches welcome). If port is zero the client connects to the default TFTP port on the server. Otherwise the specified port is used. The deprecated API is: int tftp_get(const char * const filename, const struct sockaddr_in * const server, char * buf, int len, const int mode, int * const error); int tftp_put(const char * const filename, const struct sockaddr_in * const server, const char * buffer, int len, const int mode, int * const err); The server should contain the address of the server to contact. If the sin_port member of the structure is zero the default TFTP port is used. Otherwise the specified port is used. Both API's report errors in the same way. The functions return a value of -1 and *err will be set to one of the following values: #define TFTP_ENOTFOUND 1 /* file not found */ #define TFTP_EACCESS 2 /* access violation */ #define TFTP_ENOSPACE 3 /* disk full or allocation exceeded */ #define TFTP_EBADOP 4 /* illegal TFTP operation */ #define TFTP_EBADID 5 /* unknown transfer ID */ #define TFTP_EEXISTS 6 /* file already exists */ #define TFTP_ENOUSER 7 /* no such user */ #define TFTP_TIMEOUT 8 /* operation timed out */ #define TFTP_NETERR 9 /* some sort of network error */ #define TFTP_INVALID 10 /* invalid parameter */ #define TFTP_PROTOCOL 11 /* protocol violation */ #define TFTP_TOOLARGE 12 /* file is larger than buffer */ If there are no errors the return value is the number of bytes transfered. The server is more complex. It requires a filesystem implementation to be supplied by the user, and attached to the tftp server by means of a vector of function pointers: struct tftpd_fileops { int (*open)(const char *, int); int (*close)(int); int (*write)(int, const void *, int); int (*read)(int, void *, int); }; These functions have the obvious semantics. The structure describing the filesystem is an argument to the tftpd_start: int tftp_start(int port, struct tftpd_fileops *ops); The first argument is the port to use for the server. If this port number is zero, the default TFTP port number will be used. The return value from tftpd_start is a handle which can be passed to tftpd_stop. This will kill the tftpd thread. Note that this is not a clean shutdown. The thread will simply be killed. tftpd_stop will attempt to close the sockets the thread was listening on and free some of its allocated memory. But if the thread was actively transferreing data at the time tftpd_stop is called, it is quite likely some memory and a socket will be leaked. Use this function with caution (or implement a clean shutdown and please contribute the code back :-). There are two CDL configuration options that control how many servers on how many different ports tftp can be started. CYGSEM_NET_TFTPD_MULTITHREADED, when enabled, allows multiple tftpd threads to operate on the same port number. With only one thread, while the thread is active transferring data, new requests for transfers will not be served until the active transfer is complete. When multiple threads are started on the same port, multiple transfers can take place simultaneous, up to the number of threads started. However a semaphore is required to synchronise the threads. This semaphore is required per port. The CDL option CYGNUM_NET_TFTPD_MULTITHREADED_PORTS controls how many different port numbers multithreaded servers can service. If CYGSEM_NET_TFTPD_MULTITHREADED is not enabled, only one thread may be run per port number. But this removes the need for a semaphore and so CYGNUM_NET_TFTPD_MULTITHREADED_PORTS is not required and unlimited number of ports can be used. It should be noted that the TFTPD does not perform any form of file locking. When multiple servers are active, it is assumed the underlying filesystem will refuse to open the same file multiple times, operate correctly with simultaneous read/writes to the same file, or if you are unlucky, corrupt itself beyond all repair. When IPv6 is enabled the tftpd thread will listen for requests from both IPv4 and IPv6 addresses. As discussed in the description of the tftp_server_test above, an example filesystem is provided in net/common/VERSION/src/tftp_dummy_file.c for use by the tftp server test. The dummy filesystem is not a supported part of the network stack, it exists purely for demonstration purposes. DHCP This API publishes a routine to maintain DHCP state, and a semaphore that is signalled when a lease requires attention: this is your clue to call the aforementioned routine. The intent with this API is that a simple DHCP client thread, which maintains the state of the interfaces, can go as follows: (after init_all_network_interfaces() is called from elsewhere) while ( 1 ) { while ( 1 ) { cyg_semaphore_wait( &dhcp_needs_attention ); if ( ! dhcp_bind() ) // a lease expired break; // If we need to re-bind } dhcp_halt(); // tear everything down init_all_network_interfaces(); // re-initialize } and if the application does not want to suffer the overhead of a separate thread and its stack for this, this functionality can be placed in the app’s server loop in an obvious fashion. That is the goal of breaking out these internal elements. For example, some server might be arranged to poll DHCP from time to time like this: while ( 1 ) { init_all_network_interfaces(); open-my-listen-sockets(); while ( 1 ) { serve-one-request(); // sleeps if no connections, but not forever; // so this loop is polled a few times a minute... if ( cyg_semaphore_trywait( &dhcp_needs_attention )) { if ( ! dhcp_bind() ) { close-my-listen-sockets(); dhcp_halt(); break; } } } } If the configuration option CYGOPT_NET_DHCP_DHCP_THREAD is defined, then eCos provides a thread as described initially. Independent of this option, initialization of the interfaces still occurs in init_all_network_interfaces() and your startup code can call that. It will start the DHCP management thread if configured. If a lease fails to be renewed, the management thread will shut down all interfaces and attempt to initialize all the interfaces again from scratch. This may cause chaos in the app, which is why managing the DHCP state in an application aware thread is actually better, just far less convenient for testing. If the configuration option CYGOPT_NET_DHCP_OPTION_HOST_NAME is defined, then the TAG_HOST_NAME DHCP option will be included in any DHCP lease requests. The text for the hostname is set by calling dhcp_set_hostname(). Any DHCP lease requests made prior to calling dhcp_set_hostname() will not include the TAG_HOST_NAME DHCP option. The configuration option CYGNUM_NET_DHCP_OPTION_HOST_NAME_LEN controls the maximum length allowed for the hostname. This permits the hostname text to be determined at run-time. Setting the hostname to the empty string will have the effect of disabling the TAG_HOST_NAME DHCP option. If the configuration option CYGOPT_NET_DHCP_OPTION_DHCP_CLIENTID_MAC is defined, then the TAG_DHCP_CLIENTID DHCP option will be included in any DHCP lease requests. The client ID used will be the current MAC address of the network interface. The option CYGOPT_NET_DHCP_PARM_REQ_LIST_ADDITIONAL allows additional DHCP options to be added to the request sent to the DHCP server. This option should be set to a comma separated list of options. The option CYGOPT_NET_DHCP_PARM_REQ_LIST_REPLACE is similar to CYGOPT_NET_DHCP_PARM_REQ_LIST_ADDITIONAL but in this case it completely replaces the default list of options with the configured set of comma separated options. &net-common-tcpip-manpages-sgml;