Chapter 148. Support Features

Table of Contents

148.1. TFTP
148.2. DHCP

148.1. 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.

148.2. 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_down(); // tear down unbound interfaces
    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_down();
               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.