Chapter 4 Internal Description 4.1 Source File Structure The source tree of AmiTCP/IP is dividedto few main parts: 1. Directory sys contains BSD Unix system header files, which are ported to the Amiga environment. 2. Directory net contains modules for the general networking services. They are independent of any particular protocol family. 3. Directory netinet contains the modules for the internet (inet) protocol family. IP, ICMP, TCP and UDP are internet protocols. 4. Directory kern contains modules, which provide the BSD kernel environment for the networking routines. 5. Directory api contains the BSD socket compatible shared library API code. The filesare briefly described in the following paragraphs. Note that some of the header files are actually located to the NETINCLUDE:1 directory. 4.1.1 System Include Files General Headers sys/cdefs.h Definitions to harmonize various C language dialects. sys/param.h General machine independent parameter definitions. sys/time.h Definition of structure timeval. sys/types.h Common C type definitions and file descriptorset macros for select(). BSD Socket API ________________________________ 1See section 3.1.3 on page 30. 69 70 Section 4.1 AmiTCP/IP System Manual sys/errno.h Error code definitions for system functions. sys/ioctl.h Definitions for socket IO control. sys/socket.h Definitions related to sockets: types, address families, options and prototypes. sys/uio.h IO structure definition for sendmsg() and recvmsg()2. BSD Kernel Data Structures sys/domain.h Domain structure and global domain pointer declarations. sys/kernel.h Global kernel variable definitions (used throughoutthe kernel). sys/malloc.h Defines the bsd_malloc() and bsd_free() functions. sys/mbuf.h Mbuf structure definition and inline macros. sys/protosw.h Protocol switch structure definition with related definitions (options, actions, etc.). sys/queue.h General queue data structures. sys/socketvar.h Socket structure definition and related utility macros. sys/synch.h Declarations for tsleep and spl functions. sys/syslog.h Logging related definitions. sys/systm.h System wide definitions and prototypes. 4.1.2 Protocol Independent Network Routines Network Interface net/if.c General network interface routines. net/if.h Defines the interface for network adapter drivers. net/if_arp.h General protocol independent ARP structures. net/if_dl.h Defines the Link Level sockaddr structure. net/if_loop.c Loopback device routines. net/if_sana.c Interface module for the SANA-IInetwork adapter drivers. net/if_sana.h Defines the interface for the SANA-II network adapter drivers. net/if_types.h Interface type numbers. Obsolete. net/netisr.h and net/netisr.c Defines the network input queue scheduling routines. net/sana2arp.c ARP routines for Sana-II devices. net/sana2arp.h SANA-II private ARP headers. net/sana2copybuff.c Buffer management routines called from SANA-II ________device_driver.__________ 2These functions are not implemented in this release of the AmiTCP/IP System Manual AmiTCP/IP Section 4.1 71 net/sana2errno.h SANA-II error lists and printing header. net/sana2request.c Moduleto handle SANA-II IO Requests. net/sana2request.h Inlineroutines to manage different SANA-II IO requests. net/sana2tags.c Common customization parameters for the sana_softc network interface. net/sana2tags.h Defines the tags to customize sana_softc network interface. netlib:sana2perror.c Printing routines for SANA-II error messages. Routing net/radix.c Routines for searching, adding and removing data items in the radix binary tree. net/radix.h Defines the radix tree data structures. net/route.c General, protocol independent routing functions. net/route.h Defines data structures for routing entries,tables and statistics. net/rtsock.c The socket interface to the routing information. Raw Sockets net/raw_cb.c Routines to manage the rawprotocol control blocks. net/raw_cb.h The raw protocol control block definition. net/raw_usrreq.c The raw socket interface to the low level protocols and network adapters. 4.1.3 Internet Protocol Modules Inet Domain netinet/in.c Generic Internet addressing routines. netinet/in.h Protocol numbers, port conventions, inet address definitions. netinet/in_cksum.c Calculates checksum for internet protocol headers. netinet/in_pcb.c Generic internet protocol routines, binding, addressing. netinet/in_pcb.h Declares generic internet protocol control block. netinet/in_proto.c Defines internet protocol control blocks. netinet/in_systm.h Some network byte order type definitions. netinet/in_var.h Define an interface address structure for internet. IP, ICMP netinet/icmp_var.h ICMP statistics. netinet/ip.h IP packet header, packet options, timestamp. 72 Section 4.1 AmiTCP/IP System Manual netinet/ip_icmp.c Routines to generate, receive and reflect ICMP packets. netinet/ip_icmp.h ICMP packet structure. netinet/ip_input.c IP input, packet reassemble and packet forwarding. netinet/ip_output.c IP output, packet fragmenting. netinet/ip_var.h Define IP statistics, external IP packet header, reassemble queues structures. netinet/raw_ip.c Provide a raw interface to the IP protocol. TCP netinet/tcp.h Define the TCP packet structure. netinet/tcp_debug.c TCP debugging routines. netinet/tcp_debug.h Header for TCP debugging. netinet/tcp_fsm.h TCP Finite State Machine states. netinet/tcp_input.c TCP input routines. netinet/tcp_output.c TCP output routines. netinet/tcp_seq.h TCP sequence numbering. netinet/tcp_subr.c Various TCP subroutines for initializing, connection maintenance etc. netinet/tcp_timer.c TCP timeout routines. netinet/tcp_timer.h TCP timing constants. netinet/tcp_usrreq.c Process TCP user requests (send, timeout), attach, disconnect. netinet/tcp_var.h Define TCP control block and TCP statistics. netinet/tcpip.h Define the overlaid TCPIP packet header structure. UDP netinet/udp.h Define the UDP packet structure. netinet/udp_usrreq.c Routines to UDP output, input and notification. netinet/udp_var.h Define UDPIP packet overlay, UDP statistics. 4.1.4 BSDKernel Service Modules BSD Kernel Support kern/amiga_includes.h Include file which includes all amiga specific include files. kern/amiga_main.c Main module of the AmiTCP/IP. kern/amiga_subr.h Miscellaneous function definitions (usually short inline functions). kern/amiga_time.c Timer module for the timeout functions. kern/amiga_time.h Amiga timer.device related functions. System Manual AmiTCP/IP Section 4.1 73 kern/kern_malloc.c Malloc & free related functions. kern/kern_synch.c tsleep and spl function definitions. kern/uipc_mbuf.c Mbuf functions. Socket Level Functions kern/uipc_domain.c Domain functions, especially pfslowtimo() and pffasttimo(). kern/uipc_socket.c Higher level so-level functions (socreate(), sobind() etc.). kern/uipc_socket2.c Lower level so-level functions (soisconnecting(), soqremque(), sowakeup() etc.). NETTRACE Maintenance Process Support kern/amiga_config.c Configuration and ARexx command support routines. kern/amiga_config.h Defines the structure for configuration variables. kern/amiga_cstat.c Query support routines. kern/amiga_log.c Functions for initialization of log task and code for task itself. kern/amiga_log.h Header file for logging functions. kern/amiga_netdb.c Network database parsing and support functions. kern/amiga_netdb.h Declares the network database structures and types. kern/amiga_rexx.c Arexx-interface. kern/amiga_rexx.h Arexx-interface definitions. kern/config_var.awk Awk script to generate both code and documentation from the file kern/variables.src. kern/subr_prf.c Interfaces for (s)printf(), panic() and log() kern/variables.src Definition and documentation for configuration variables 4.1.5 BSDSocket API Shared Library Interface api/allocdatabuffer.c Client data buffer allocation functions. api/allocdatabuffer.h Client data buffer allocation definitions. api/amiga_api.c Contains API initialization and deinitialization functions and data. Opening and Closing of the AmiTCP/IP socket library bases. api/amiga_api.h Description of the SocketBase structure (forinternal AmiTCP/IP use only), and some inline functions relatedto the API functionality. 74 Section 4.1 AmiTCP/IP System Manual api/amiga_libcallentry.h Included in every module which defines AmiTCP/IP socket library calls. api/amiga_libtables.c Exec library base list and AmiTCP/IP socket library function tables for MakeLibrary(). api/amiga_raf.h Compiler dependent macros for Register Argument Functions. api/apicalls.h Includes either api/apicalls_gnuc.h or api/apicalls_sasc.h. api/apicalls_gnuc.h Inline functions for internal API calls for GNUC. api/apicalls_sasc.h Inline functions for internal API calls for SASC. API Functions api/amiga_generic.c General Unix system calls related to file descriptors ported for AmiTCP/IP socket library. There are also some Amiga specific extensions to the BSD Unix system calls in this module. api/amiga_libcalls.c Inet library functions (link library in Unix system). These inet functions are provided as a part of the AmiTCP/IP socket library. api/amiga_syscalls.c Standard BSD style socket functions ported to the AmiTCP/IP socket library. api/gethostnamadr.c Functions that calls resolver in order to obtain host information from nameserver. Calls netdatabase functions if information can not be resolved. api/gethtbynamadr.h Prototypes for two functions in api/getxbyy.h. api/getxbyy.c Host (without nameserver), network, service ja protocol query functions (also a link library in unix system). api/hostbuf.h Structure definition for host queries and few minor netdb stuff. Resolver Functions api/arpa_nameser.h Information for nameserver query. api/res_comp.c Routines to translate domain names between conventional ascii and the compressed format used in queries. api/res_debug.c Functions that output debugging information3 api/res_init.h Resolver initializer function. Very simple in AmiTCP/IP implementation. api/res_mkquery.c Function that forms a domain name query in buffer. api/res_query.c Functions to generate query sequence and append domains to incomplete hostnames. api/res_send.c Function to send and receive query to and from a nameserver, respectively. api/resolv.h Resolver datatypes, defines, variables and prototypes. ________________________________ 3If RES_DEBUG defined at compile time System Manual AmiTCP/IP Section 4.2 75 4.1.6 Miscellaneous Files Makefiles GCCOPTS Compiler options forthe GCC. GNUmakefile Makefile used with GNU make in HP-UX workstations, where the source tree is maintained. SCOPTIONS Compiler options for the SAS/C 6.x Smakefile Makefile for SAS/C smake 6.x to compile the AmiTCP/IP. Revision Support bsdsocket.library_rev.rev Contains revision numberof the current version (number of the latest build). This file is updated with the BumpRev utility4. bsdsocket.library_rev.h Automaticallygenerated by BumpRev. Contains build date, version and revision strings. SAS/C 6.x GST Support all_includes.c C file for GST generation. Just includes the all_includes.h file. all_includes.h Includes all header files needed by theAmiTCP/IP, which are suitable for inclusion in a GST. Note that any headers with inline functions can not be included in a GST. Other Files conf/conf.h Static configuration information in form of preprocessor defines. Every C module must include this as the first include file. conf/rcs.h RCS header inclusion macro for GCC. InSAS/C this macro is defined in the SCOPTIONS file. protos/*/*.h Prototypes for functions in various modules. 4.2 AmiTCP/IP Initialization All more or less distinct modules of theAmiTCP/IP must be initialized before they can be used for the real work. Initialization is done in module kern/amiga_main.c, which definesfunctions init_all() and deinit_all(). The main() function calls the init_all()after it has done all needed local initializations. If init_all() failsthe deinit_all() is called to clean up. ________________________________ 4The BumpRev is supplied by Commodore. 76 Section 4.2 AmiTCP/IP System Manual 4.2.1 init _all() This function calls the initialization routines of all modules which have such. This must be done carefully in the correct partial order. There arefew general heuristics which can be applied on initialization (with respect to the ordering requirements): 1. Initialize the modules for which the initialization cannot fail. This includes semaphore initializations and such. 2. Initialize the modules which are most likely to fail, e.g. large memory allocations, modules which require recent versions of some libraries etc. 3. Initialize modules left over by rules 1 and 2. Semaphoreinitializations are first, because later steps may use them to protect against race conditions. The initialization process of the AmiTCP/IP is fully reversible. If the initialization fails in any step thedeinit _all() function can be used to collect the garbage. init_all() does the initializations in following order, returning nonzero if all steps succeed. Numbers in the parenthesis are the section and page numbers for more information, respectively: 1. malloc_init() initializes the malloc_semaphore. This is first since later steps may want to use bsd_malloc() to allocatememory (5.2.2, 95). 2. spl_init() initializes the priority level subsystem,which is used throughout the code to protect critical sections (5.3.1, 96). 3. sleep_init() initializes the sleep_semaphore and the sleep queues (5.3.2, 97). 4. readconfig() reads in and parses the command line arguments and the configuration file. This is the first phase which is expected to fail. This is done at the beginning in order to allow other initialization functions to use the configuration information given. Note that since the logging subsystem is not initialized yet, readconfig() cannot log any errors encountered (4.11, 91). 5. log_init() initializes the logging subsystem. From now on other initialization functions can log needed error messages (4.8, 89). 6. mbinit() allocates memory to be used by the protocols. This is a candidate to fail if the memory is nearly exhausted (4.5, 79). 7. timer_init() initializes the timeout module. This requires version 36 or greater of the operating system. Returns the signal mask to wait for the timeout messages (5.1, 93). 8. api_init() initializes and creates the master socketbase structure (4.10.2, 91). 9. res_init() initializes resolver structure and semaphore. System Manual AmiTCP/IP Section 4.3 77 10. sana_init() initializes the SANA-II network interface module. This returns the signal mask to be used for waiting the network related messages (4.7, 84). 11. domaininit() initializes all configured protocols. This is left at the end of the initialization, since this requires the other parts of the system to be initialized. 12. readnetdb() initializes and reads in the network data base information from ENV:AmInet/netdb (4.12, 92). 13. api_show() makes the shared library interface visible on the Exec library list (4.10.2, 91). 4.2.2 deinit _all() deinit_all() is the reverse of init_all(); it deinitializes required modules in reverse order with respect tothe initialization. If the initialization process for a module does not allocate any resources, then there is no deinitialization function for that module. The deinitialization functions called are (in this order): 1. api_hide() removes the library from the Exec librarylist, so no-one can open the library any more (4.10.2, 91). 2. sana_deinit() deinitializes the network driver module (4.7, 84). 3. api_deinit() deinitializes the API (4.10.2, 91). 4. timer_deinit() deinitializes the timer module (5.1,93). 5. mbdeinit() frees all memory used by mbufs (4.5, 79). 6. log_deinit() deinitializes the logging subsystem (4.8, 89). 4.3 The Main Module The main() function is defined in the file kern/amiga_main.c. On startup it initializes all modules by calling init _all(). After a successful initialization it first starts the AmiTCP/IP timeouts by calling timer_send(). Then it enters the event loop. AmigaOS function Wait() returns when one of the signals specified in the signal mask given as argument is received. The mask currently specifies signals for SANA-II drivers, timeouts and the break signal. When some set of these signals is received the Wait() returns and its return value, which indicates the received signals, is checked. The functions sana_poll() and timer_poll() are each called in turn if their signal was received. They each return boolean tellingif they would like to be called again before waiting again. The loop in which these functions are called is terminated either when no one of the poll functions wants to continue or when thebreak signal is received. The pollfunctions do handle only one message at a time, so that any of them should not starve. 78 Section 4.4 AmiTCP/IP System Manual When thebreak signal is received the AmiTCP/IP terminates if no library bases opened by API users are open any more. On termination all reserved resources are freed by callingdeinit _all(). 4.4 Protocol Entities One design goal was to keep the protocolentities intact. This is achieved through implementing all external dependencies of the protocol entities. Fortunately the protocol entities in BSD arehighly independent of other UNIX kernel services. For example, all dynamic memory management is done through the memory buffer5 abstraction, which means that we only had to provide the mbuf interface and the problem of memory management was solved. All protocol entities in a protocol family are defined in terms of a protocol switch structure. This structure is fully defined in the header file sys/protosw.h and [Leffler et al 1991b ]. The protocol switch structure is defined as follows: struct protosw - short pr_type; /* socket type used for */ struct domain *pr_domain; /* domain protocol a member of */ short pr_protocol; /* protocol number */ short pr_flags; /* protocol flags */ /* protocol-protocol hooks */ void (*pr_input)(); /* input to protocol (from below) */ int (*pr_output)(); /* output to protocol (from above)*/ void (*pr_ctlinput)(); /* control input (from below) */ int (*pr_ctloutput)(); /* control output (from above) */ /* user-protocol hook */ int (*pr_usrreq)(); /* user request hook */ /* utility hooks */ void (*pr_init)(); /* initialization hook */ void (*pr_fasttimo)(); /* fast timeout (200ms) */ void (*pr_slowtimo)(); /* slow timeout (500ms) */ void (*pr_drain)(); /* flush any excess space possible */ "; Note thatthe actual prototypes for the function pointers are omitted, see the file sys/protosw.h for full definition. When protocol is started the pr_init() is called first to allow the protocol to initialize all needed internal structures. Then the input process will call pr_fasttimo() and pr_slowtimo() entries periodically if defined6. The pr_drain() entry asks the protocol tofree all non-critical memory buffers in a low-memory situation. Protocolscall each other through the protocol--protocol interface. To pass a packet up in the hierarchy a protocol calls the pr_input()-entry of the protocol above it. pr_output()-entry is called when a protocol wishes to pass a packet down in the protocol hierarchy (towards network). ________________________________ 5or mbuf for short 6Member function is defined when the value of its address is not NULL. System Manual AmiTCP/IP Section 4.5 79 Protocolssend control information to each other through the pr_ctlinput() and pr_ctloutput() entries. All requests coming from the API are dispatched through the pr_usrreq() entry. 4.5 Memory Management As stated earlier, the memory managementof the protocol stack is done with memory buffers. An mbuf is a structure containinglittle amount of storage (usually 128 bytes). Some bytes of this storage are used for the header, but most of it is used to storeuser data. These small buffers are linked together to get storage for larger data. Mbufs arehighly efficient in a network protocol environment where it must be able to attach and strip protocol headers with minimum overhead and most importantly, without copying the data as doing so. When data is stored in an mbuf chain, attaching a header is achieved by simply linking the mbuf containing the header to the head of the chain. Removing a header is also done simply by removing the first mbuf or by incrementing the data pointer inside the mbuf. In general there are two types of mbufs. Ones with an packet header and ones without. An Mbuf with packet header is used as the first mbuf of every packet. This header contains extra information needed per packet. See the header file sys/mbuf.h for the mbuf header definition. Mbufs canbe chained in two dimensions. First they may be linked to form the storage for the whole message. Second these messages may be linked together so that the boundaries of messages are maintained. This second feature is mainly used by messageoriented protocols such as UDP. To gain efficiency an mbuf may have a reference to external memory page (a cluster), where a big message is copied instead of splitting it apart to many mbufs. The main advantage of this feature is avoidance of copying the data when sending it with TCP, since the clusters are shared between copies7. 4.5.1 Mbuf Functions Mbufs are accessed through set of functions which can be grouped as follows: Maintenance int mb_check_conf(void *dp, LONG newvalue) Check configurable variable whose address is dp. Return TRUE if the newvalue is acceptable value for that variable. See section 4.11 for information about the configuration. BOOL mbinit(void) ________________________________ 7TCP must keep a copy of the sent data for possible retransmissions. 80 Section 4.5 AmiTCP/IP System Manual Initialize the whole mbuf-subsystem. Allocate a chunk of mbufs and clusters (using m_alloc() and m_clalloc). Must be called before any other mbuf-related function (except that mb_check_conf() can be called anytime). void mbdeinit(void) Free all resources used by mbuf subsystem. Must be called as the last mbuf-related function in the program. BOOL m_alloc(int howmany, int canwait) Allocate howmany mbufs and place them on the mbuf free list. If canwait is true the caller can wait if memory is not readily available. BOOL m_clalloc(int ncl, int canwait); Allocate ncl mbuf clusters and place them on the cluster free list. struct mbuf *m_retry(int i, int t) Ask protocols to free space when short of memory and re-attempt to allocate an mbuf. void m_reclaim(void) Ask protocols to free space when short of memory. Allocation and Deallocation struct mbuf *m_get(int canwait, int type) Allocate an mbuf of type type. If no mbufs are available we can wait for them if canwait is M_WAIT. Initialize mbuf to contain internal data. void MGET(struct mbuf *m, int canwait, int type) This is an macro form of above. struct mbuf *m_gethdr(int canwait, int type) Allocate an mbuf of type type. If no mbufs are available we can wait for them if canwait is M_WAIT. Initialize mbuf to contain a packet header and internal data. void MGETHDR(struct mbuf *m, int canwait, int type) This is a macro form of above. struct mbuf *m_getclr(int canwait, int type) Allocate an mbuf of type type. If no mbufs are available we can wait for them if canwait is M_WAIT. If allocation succeeds the data buffer is zeroed before returning. MCLALLOC(struct mcluster *p, int canwait) A macro to allocate an mbuf cluster. The result is placed in p. System Manual AmiTCP/IP Section 4.5 81 MCLGET(struct mcluster *p, int canwait) A macro to add a cluster to a normal mbuf. M_EXT flag of the mbufis set on success. struct mbuf *m_free(struct mbuf *m) Free an mbuf. Next mbuf in the chain is returned, if any. void MFREE(struct mbuf *m, struct mbuf *n) This is an macro form of above. Successor mbuf is returned in n. MCLFREE(struct mcluster *p) A macro to free an mbuf cluster. void m_freem(struct mbuf *m) Free the whole chain of mbufs starting from m. Utility Functions struct mbuf *m_copym(struct mbuf *m, intoff0, int len, int canwait) Make a copy of an mbuf chain starting off0 bytes from the beginning of m, continuing for len bytes. If len is M_COPYALL, copy to endof mbuf. void m_copydata(struct mbuf *m, int off,int len, caddr_t cp) Copy data from an mbuf chain starting off bytes from the beginning, continuing for len bytes, into buffer cp. struct mbuf *m_prepend(struct mbuf *m, int len, int canwait) Prepend a chain of mbufs (m) with new mbuf with len bytes allocated from the first mbuf aligned on a long word boundary. void M_PREPEND(struct mbuf *m, int plen,int canwait) Macro version of above optimized for the most general cases. void M_COPY_PKTHDR(struct mbuf *to, struct mbuf *from) Macro for copying an mbuf packet header from from to to. from must have flag M_PKTHDR set, and to must be empty. void M_ALIGN(struct mbuf *m, int len) Macro to set the m data pointer of a newly--allocated mbuf (with m_get()/MGET()) to place an object of the size len at the end of the mbuf, long word aligned. void MH_ALIGN(struct mbuf *m, int len) As above, but for mbufs allocated with m_gethdr()/MGETHDR() or initialized by M_COPY_PKTHDR(). int M_LEADINGSPACE(struct mbuf *m) Compute the amount of space available before the current start of data in an mbuf. 82 Section 4.6 AmiTCP/IP System Manual int M_TRAILINGSPACE(struct mbuf *m) Compute the amount of space available after the end of data in an mbuf. void MCHTYPE(struct mbuf *m, type t) Change mbuf to a new type. void m_cat(struct mbuf *m, struct mbuf *n) Concatenate mbuf chain n to m. Both chains must be of the same type. m_adj(struct mbuf *mp, int req_len) Trim req_len bytes from the head of the mbuf chain mp if req_len is positive, else trim -- req_len bytes from the tail of the mbuf chain. struct mbuf *m_pullup(struct mbuf *n, int len) Rearrange an mbuf chain so that len bytes from the beginning of the mbuf chain n are contiguous and in the data area of the mbuf so that the data can be used as a structure. Utility Macros type t mtod(struct mbuf *m, type t) Convert mbuf pointer to a pointer to the start of the data area of the mbuf casted to type t. struct mbuf *dtom(type *x) Convert data pointer within an mbuf to mbuf pointer. 4.6 Concurrency Control The protocol implementation in the BSD net/2 is driven by network and timer interrupts and user processes calling the system functions. As the whole protocol stack is moved inside normal AmigaOS process, some modifications are in place. The processor priority levels are the main concurrency control tool of the BSD kernel. The levels defined are SPL0 (user level), SPLSOFTCLOCK, SPLNET and SPLIMP (the most privileged level). Execution at a higher level disables the execution at all lower levels. InAmiTCP/IP the concurrency control is implemented either with semaphores (when debugging) or with prevention of the task switches (Forbid()/Permit()), see section 5.3.1 on page 96 for the implementation notes. The protocol input and timeouts are driven by a single process that manages the whole protocol stack. The process sends appropriate IO requests to the timer device and the SANA-II device drivers in question. Actions are then taken as response to the returned requests. Before any protocol routines are called the priority level is raised either to SPLSOFTCLOCK or SPLNET. After the function returns the priority level is lowered back to SPL0 and the request issent back to the device driver. System Manual AmiTCP/IP Section 4.7 83 On the API side the concurrent execution of system calls is mostly prohibited, because in UNIX the system calls are atomic in the sense that there is never more than one system callin execution. In AmigaOS the shared library functions must be re-entrant so the protection must be provided by the library functions themselves. The priority at which the main process runs must be above the default value of 0 to provide enough time to process the networking protocols. On the other side there is no sense to drive the main process at greater priority than the SANA-II device drivers. 4.7 Network Device Drivers AmiTCP/IP uses standard SANA-II driversas its external network device drivers. A little glue is needed to attach a SANA-II driver into BSD net/2 code. Network Interface The BSD net/2 networking code provides aclean interface to the network device drivers. The network interface provides a consistent interface for all protocols that may be present inthe BSD Unix kernel. Each hardware device is associated with an unique network interface which may be used by one or more protocol families. The network interface is flexible enough to attach different SANA-II network device drivers into the AmiTCP/IP networking system. Common part of all network interfaces is described in [Leffler et al 1991b ]: struct ifnet - char *if_name; /* name, e.g. "en" or "lo" */ short if_unit; /* sub-unit for lower level driver */ short if_mtu; /* maximum transmission unit */ short if_flags; /* up/down, broadcast, etc. */ short if_timer; /* time 'til if_watchdog called */ int if_metric; /* routing metric (external only) */ struct ifaddr *if_addrlist; /* linked list of addresses per if */ struct ifqueue - struct mbuf *ifq_head; struct mbuf *ifq_tail; int ifq_len; int ifq_maxlen; int ifq_drops; " if_snd; /* output queue */ /* procedure handles */ int (*if_init)(); /*init routine */ int (*if_output)(); /* output routine (enqueue) */ int (*if_start)(); /* initiate output routine */ int (*if_done)(); /*output complete routine */ int (*if_ioctl)(); /* ioctl routine */ int (*if_reset)(); /* bus reset routine */ int (*if_watchdog)(); /* timer routine */ /* generic interface statistics */ int if_ipackets; /* packetsreceived on interface */ 84 Section 4.7 AmiTCP/IP System Manual int if_ierrors; /* input errors on interface */ int if_opackets; /* packetssent on interface */ int if_oerrors; /* output errorson interface */ int if_collisions; /* collisions on csma interfaces */ /* end statistics */ struct ifnet *if_next; "; Network interface for SANA-II devices are handled in the module net/if_sana.c. This is the only module aware of SANA-II devices inside the AmiTCP/IP network process. It hides most SANA-II specific details from the rest of the code. Module Initialization ULONG sana_init(void) This initialization routine is called at startup time before any interfaces have been added to system. It creates the common message port used for all SANA-II network interfaces. It also attaches the loopback device into system. It returns the signal mask of the message port, if its creation was successful. void sana_deinit(void) This routine frees all resources allocated by the SANA-II interface module. It aborts all pending IO requests, frees them, closes network device drivers and frees the corresponding network interfaces. Finally it deletes the message port. 4.7.1 SANA-II Soft Network Interface A message passing system based on the normal Exec IO requests is used to transfer packets between the AmiTCP/IP and SANA-II devices. The IO can be either synchronous or asynchronous. The SANA-II interface module has a message port, which receives all fulfilled or aborted asynchronous IO messages. A dispatch method (currently a dispatch function pointer) have been added to all asynchronously sent IOrequests. Dispatching function handles the received message in an appropriate way. A messagemay contain received packet, some buffers allocated for the sent message or an event mask. Dispatcher functions feed the received data to the protocol input queues. If needed, the protocol input routines are run. Dispatchers also free the memory allocated for the sent packets, or relays events to the higher level protocols. Because the interface for the SANA-II device driver must handle many different protocols and network adapters, it has some private data hidden from the rest of the system. The struct sana_softc network interface is defined in file net/if_sana.h: /* * SANA-II Interface descriptor * NOTE: most of the code outside will believe this to be simply * a "struct ifnet". The other information is, on the other hand, * our own business. System Manual AmiTCP/IP Section 4.7 85 */ struct sana_softc - struct ifnet ss_if; /* network-visible interface */ struct in_addr ss_ipaddr; /* copy of ip address */ ULONG ss_hwtype; /* wiretype */ UBYTE ss_hwaddr[MAXADDRSANA]; /* Generalhardware address */ struct Device *ss_dev; /* pointer todevice */ struct Unit *ss_unit; /* pointer to unit */ VOID *ss_bufmgnt; /* magic cookie for buffer mngement */ UWORD ss_reqno; /* # of requests to allocate */ struct IOIPReq *ss_reqs; /* allocated requests*/ struct MinList ss_freereq; /* free requests */ #if INET struct - UWORD reqno; /* for listening ip packets */ UWORD sent; ULONG type; " ss_ip; struct - /* for ARP */ UWORD reqno; UWORD sent; ULONG type; /* ARP packet type */ ULONG hrd; /*ARP header type */ struct arptable *table; /* ARP/IP table */ " ss_arp; #endif /* INET */ UWORD ss_rawreqno; /* for raw packets */ UWORD ss_rawsent; struct sana_softc *ss_next; char ss_name[IFNAMSIZ]; /* namelives here */ "; There isan external interface to this structure via SIOCSSANATAGS and SIOCGSANATAGS ioctls. struct ifnet *iface_find(char *name) This function initializes a network interface for given interface. It is called on a non-existent interface from ifunit(). It tries to open the appropriate SANA-II device driver, and if successful it initializes a descriptor and calls if_attach() with it. Its argument is the device driver name concatenated with a slash and unit number. This name is used by AmiTCP/IP to open appropriate unit of the SANA-II driver. The initialization routine provides the specified SANA-II network device unit with the CopyToBuf() and CopyFromBuf() function tags8. Those tags are used to copy data to and from the internal buffers of the network device. The taglist given to the device driver is defined in file net/sana2copybuff.c. After successful open iface_find() initializes the appropriate members of the new sana_softc structure. It stores the device and unit pointers, magic cookie for buffer management, hardware type, MTU ________________________________ 8For discussion for these functions, see [SANA-II 1992 add ] 86 Section 4.7 AmiTCP/IP System Manual and address length. It also searches for the hardware type specific taglist, and sets the rest of interface parameters according the taglist. Next the interface initialization routine attaches the new network interface into ifnet list with if_attach(). Then it initializes interface with if_init(). Interface Routines BOOL sana_poll(void) The AmiTCP/IP processes received messages by calling this function. It effectively hides the actual implementation from the rest of the system. This routine is called when AmiTCP/IP receives the signal allocated by sana_init(), and it returns TRUE, if itshould be called again before Wait(). sana_poll() retrieves messages from the message port, dispatches them and then runs the input queues. int sana_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst, struct rtentry *rt) This function is used as the if_output() method. It tries to get a free IO request from the ss_freereq list. If no free request is available it drops the packet. It attaches the packet m0 to request, sets dispatching function to free_written_packet() and sends the request to the device driver. The raw packets to the SANA-II interface use the following variation of socket address. The addressing family of the raw packets must be AF_UNSPEC. Currently only the ARP uses the raw SANA-II packets. /* * A socket address for a generic SANA-II host */ struct sockaddr_sana2 - u_char ss2_len; u_char ss2_family; u_long ss2_type; u_char ss2_host[MAXADDRSANA]; "; void sana_ioctl(struct ifnet *ifp, int cmd, caddr_t data) This function is used as the if_ioctl() method. SANA-II devices and their respective network interfaces are configured via raw sockets by the IoctlSocket() requests. When the sana_ioctl() gets the SIOCSIFADDR request, it changes the IP address of the interface. The SIOCGIFFLAGS ioctl is used to set the parameter flags of the interface9. Special SANA-II configuration is done with SIOCSSANATAGS ioctl. It passes a tag list to the parse_sana_param_tags() function. ________________________________ 9See file sys/ioctl.h for full listof all ioctls System Manual AmiTCP/IP Section 4.7 87 void if_down(struct ifnet *ifp) This function pulls into if_ioctl() from net/if.h. if_down() marks the interface down and informs all affected network protocols about the matter. If the interface handles SANA-II device, it calls sana_down(), which handles the dirty work to put theinterface down. static void sana_run(struct sana_softc *ssc, int n, struct ifaddr *ifa) sana_run() configures the SANA-II interface and allocates the IO requests to use with the SANA-II device driver. Because the SANA-II device can be configured only once (see S2_CONFIGINTERFACE in [SANA-II 1992 add ]) the initialization routine does not configure it. Among other things the hardware address of the network adapter is set in configuration process. This function is called by the SIOGSIFADDR ioctl, which also sets the protocol address of the interface. static void sana_up(struct sana_softc *ssc) sana_up() marks interface up and enables the interface to listen the packets from the network. It sends read request with an appropriate packet type number and dispatch function to the device. static BOOL sana_down(struct sana_softc*ssc) sana_down() aborts all pending requests sent to a SANA-II device driver. static void sana_ip_read(struct sana_softc *ssc, struct IOIPReq *req) and static void sana_arp_read(struct sana_softc *ssc, struct IOIPReq *req) These dispatch functions are used to feed data from a network device to the protocol input queues. The network interface has not the input routines as members. Dispatching functions allocate mbufs for the next packet and send IO requests again to the network device. Statistics BSD Network interface contains a lot ofexcessive statistical data. Most of it is made redundant by the statistics gathered by the SANA-II driver. Because network statistics are not retrieved by looking at /dev/kmem, there is no need to gather BSD compatible statistics. A public ARexx port, named AMITCP, is set up for statistics retrieval and we can use appropriate SANA-II commands to get needed data when asked. 4.7.2 ARP The requirements for the ARP implementation for a SANA-II interface differ radically from the original implementation BSD. The original code was written exclusively for the Ethernet, which has a global addressing scheme and a fixed address length. The SANA-II ARP (in the module net/sana2arp.c) holds a separate address mapping cache for each interface. The number of entries in the cache may 88 Section 4.8 AmiTCP/IP System Manual be configured at the run time. The hardware address length varies from one interface to another. The mapping caches are hashtables with linked lists, so there is no limitations in thebucket size. ARP Table locking is done with a signal semaphore insteadof spln() functions. The ARP table is externally accessed only by IoctlSocket() calls. With the new SIOCGARPT ioctl the whole ARP cache may be read at once. There is no need to awkwardly read /dev/kmem. 4.8 Logging As everything is not predictable, programs like to inform the user about certain situations to help the user and/or maintainer to get programs work better. This is the motivation for log() and panic() functions. The fact that the file I/O routines can't be called from interrupts must be taken into account, since a program may want to inform the user even while executing at an interrupt level. Among thevery few functions of the AmigaOS which are callable from interrupts are GetMsg() and PutMsg(). These are used to implement the logging subsystem. When AmiTCP/IP has something to tell to the user, it first checks if there is any free messages available. There is only alimited number of these messages to use, since they are preallocated during initialization10. If there is no message available, a counter indicating that the message could not be deliveredis incremented. If a free message is available, the text given bycaller is printed into the buffer of the message. Then the message is sent to the private port of the NETTRACE task. The NETTRACE task is created early in the initialization of the AmiTCP/IP. This task waits for incomingmessages and when one arrives, it prints the message to the log windowand/or log file. Then message is freed by sending it back to AmiTCP/IP for reuse. If loss of log messages is detected, log() is called totell that to the user. The functions are defined as follows: int log(unsigned long level, const char*fmt, ...) This logs a message with format specified in exec.library/RawDoFmt(), similar to the printf(). It is preceded with ``'' where N is a level as defined in file sys/syslog.h. void panic(const char *fmt, ...) When this function is called, we are in BIG trouble. If task, which called this, is not AmiTCP/IP, we just halt this task and send a message to AmiTCP/IP to halt immediately and free all resources. Format is as in exec.library/RawDoFmt(). If AmiTCP/IP runs into a panic() it first patches all API functions to return an error code to the caller. Then, if the panic() was not in the context of the AmiTCP/IP it signals the AmiTCP/IP and halts. As the AmiTCP/IP receives the signal, it send a message to the log ________________________________ 10This is because memory cannotbe allocated from interrupt code. System Manual AmiTCP/IP Section 4.9 89 and signals all application programs waiting for network to take attention. As this is done, it opens an User Requester to inform the user. After the user responds, AmiTCP/IP waits for all library openers to close libraries and finally unloads itself from the memory. int printf(const char *fmt, ...) Like the normal C--library printf (with format of exec.library/RawDoFmt()) except that the printing is done using the logging mechanism. int sprintf(char *buf, const char *fmt,...) As in a normal C--library. Format is as in exec.library/RawDoFmt(). All functions (except panic()) return number of printed (or logged) characters. Initialization Routines BOOL log_init(void) This function initializes NETTRACE subsystem by opening intuition.library for opening UserRequest in the case of panic(). The the log messages are initialized to use by preallocating memory for them. Then NETTRACE is started and AmiTCP/IP waits for a signal from it. If NETTRACE success in it's initialization, then it sends a message back, which is then replied. If initialization fails, a variable is set to specific value and CTRL-F is sent to the AmiTCP/IP. If all this succeeds, the log messages are initialized and sent to logReplyPort which works as a queue for the free messages. void log_deinit(void) This works as reverse to initialization process. If NETTRACE is still running, a message is sent to it telling it to terminate. Then AmiTCP/IP waits until the message is replied. Then the memory reserved by the messages can be freed. Finally the intuition.library is closed. 4.9 ARexx Interface The ARexx port of the AmiTCP/IP is maintained by the NETTRACE task. The messages are parsed with parseline() (defined in kern/amiga_config.c). Initialization Routines ULONG rexx_init(void) This initialization routine is called at the startup time of the NETTRACE process. It opens the utility.library and the rexxsyslib.library to be used by the ARexx code and creates a public 90 Section 4.10 AmiTCP/IP System Manual ARexx message port. The signal mask of the ARexx port is returned upon a successful initialization. void rexx_deinit(void) Free all resources allocated by the ARexx interface module. First the ARexx port is removed from the system's list of message ports so that no-one is able find the port any more to send new messages. Then all pending messages are returned with error code set. Finally the ARexx port is deleted and libraries opened by rexx_init() are closed. Reply Routine BOOL rexx_poll(void) Checks if any ARexx messages has arrived and handles them one at a time. The parseline() function is used to parse and execute the given command. Returns TRUE if there might still be messages to handle, otherwise the return value is FALSE. 4.10 Application Interface Concepts 4.10.1 SocketBase -- an Extension of the Task Structure In Unix systems, where the network codeis integrated into the kernel, a process structure holds fields for per-process information of network related data. In AmiTCP/IP, where socket API is implemented as a shared library, each opener gets a newly created library base that holds data used by the AmiTCP/IP system. Each library base function makes sure that the caller is from the right Amigatask and refuses to operate if wrong task is attempting to use it (seesection 5.5 on page 100 for detailed information). 4.10.2 The System Call Semaphore and Task Priorities Currently, when program enters to some of socket library functions, it attempts to get semaphore to hold othercallers executing library code simultaneously. This is done so, since in Unix system,where this code originally runs, doesn't pre-empt process that is executing system call. In BSDSS, where ``Unix system calls'' run in user mode, system call emulation glue uses a mutex to prevent simultaneous use of that part of the server code. Although spl functions are used in NET/2 code to prevent simultaneous access of criticalsections, there may still be some sections that leave out protection if system call semaphore is removed. Unnecessary system call semaphore usageis going to be removed in later releases. Hopefully it, and the overhead it generates,becomes obsolete. The priority of the application process is raised to the same with the AmiTCP/IP, while the application executes AmiTCP/IP code. This is to prevent situations where a process witha low priority gets blocked for a long time while holding e.g. the system call semaphore, since otherwise System Manual AmiTCP/IP Section 4.11 91 all networking programs would be blockedwith it. Thisapplies to all semaphores used internally by the AmiTCP/IP, not just the system call semaphore. Initialization Routines Making application interface visible andoperative contains a few steps: call to api_init() creates the master socket library base and initializes semaphores and lists API needs. Library base is not inExec library base list yet. Routine api_show() checks first if bsdsocket.library is already in Exec list and if not, calls Exec AddLibrary() to make it visible. AmiTCP/IPcan remove socket library from the Exec list at any time, i.e. make it not visible, by calling api _hide(). No new socket bases can be opened after this call. Socket bases opened before api _hide() operate normally. If AmiTCP/IP calls api_show() again, new libraries can be opened. api_setfunctions() takes all socket library bases out of operation. It sets all function vectors in every socket base to return an error. This function is called if AmiTCP/IP panic()ed and all libraries are expected to be closed. When application interface is to be removed from system, api_deinit() is called. It waits for all opened libraries to closeand then calls expunge function to deallocate the master socket base from the memory. 4.11 Configuration Variables The configuration variable definitions are stored into a structure named cfg_variable. It is defined in kern/amiga_netdb.h as follows: /* Variable types */ /* Note: Query calls value, Set calls notify functions */ enum var_type - VAR_FUNC = 1, /* value is function pointer */ VAR_LONG, /* value is pointer to LONG */ VAR_STRP, /* value is pointer to string */ VAR_FLAG, /* LONG value is set once */ VAR_INET, /* struct sockaddr_in */ VAR_ENUM /* value is pointer to long, whose value is set according to a enumeration string in notify*/ "; typedef LONG (*var_f)(struct CSource *args, UBYTE **errstrp, struct CSource *res); typedef int (*notify_f)(void *pt, LONG new); /* Configureable variable structure */ struct cfg_variable - enum var_type type; /* type of value */ 92 Section 4.12 AmiTCP/IP System Manual WORD flags; /* see below */ const UBYTE *index; /* optional index keyword list */ void *value; /* pointer to value... */ notify_f notify; /* notification function */ "; #define boolean_enum (notify_f)"NO=FALSE=OFF=0,YES=TRUE=ON=1" /* Variable flags */ #define VF_TABLE (1<<0) /* with an index... */ #define VF_READ (1<<1) /* readable */ #define VF_WRITE (1<<2) /* writeable */ #define VF_CONF (1<<3) /* writeable only during configuration */ #define VF_RW (VF_WRITE_VF_READ) #define VF_RCONF (VF_CONF_VF_READ) #define VF_FREE (1<<8) /* free when replaced? */ The configuration file (by default AmiTCP:db/AmiTCP.config) is read with the function readconfig(). This function also parses the command line arguments. 4.12 Network Database The network database is initialized by the init _netdb() function. This function allocates the NetDataBase structure and parses the file AmiTCP:db/netdb. The NetDataBase structure is definedas follows: struct NetDataBase - struct SignalSemaphore ndb_Lock; struct MinList ndb_Hosts; struct MinList ndb_Networks; struct MinList ndb_Services; struct MinList ndb_Protocols; struct MinList ndb_NameServers; struct MinList ndb_Domains; "; This structure contains a lock and lists for the different network database entries. The lock semaphore is obtained in the shared mode for reading, and in the exclusive mode for writing. See section 2.5.1 for the information about the different entries. Chapter 5 Implementation Notes This chapter describes some points of the implementation of the AmiTCP/IP. The code that is not changedfrom the BSD net/2 -release is not reviewed. [Leffler et al 1989 ] describes the design and implementation of the BSD Unix, including the networking system. [Leffler et al 1991b ] is also very helpful. Most of the knowledge gathered during this project is gained by reading the source code itself. This chapter does not try to make that totally unnecessary. 5.1 Time outs The Unix timeout() function implements the time out needs of the Unix kernel. When kernel code calls timeout(), the functiongiven as argument will be called after the specified timeout has elapsed. In AmigaOS time outs are provided by the timer device. AmiTCP/IP sendstime out requests to the device and gets them back when the time specified has elapsed. The functions called by the time out service are: if_slowtimo() This is the time out function of the network interfaces and is defined in net/if.c. arptimer() Handles the time outs of ARP protocol (net/sana2arp.c). pfslowtimo() This is the main slow time out function for all protocols. It calls the pr_slowtimo() function of each configured protocol. The interval of this timer is 500 ms (kern/uipc_domain.c). pffasttimo() Is the corresponding function for fast (200 ms) time outs. 93 94 Section 5.1 AmiTCP/IP System Manual The request structure used by AmiTCP/IP has few fields in addition to the normal struct timerequest1. The structure is defined in kern/amiga_time.h as follows: struct timeoutRequest - struct timerequest timeout_request; /* timer.device sees only this */ struct timeval timeout_timeval; /* timeout interval */ TimerCallback_t timeout_function; /* timeout function to be called */ "; In this implementation the time out functions themselves do not call any time out services, but the functionsare called by timer_poll(), which is called by main() when there might be a message to handle (e.g. when the timer signal is received). timer_poll() checks the reply port to see if there really is a message to handle. Afterthe time out function is serviced timer_poll() sends the request back to the timer device and returns. Time outservice is implemented by files kern/amiga_time.c and kern/amiga_time.h. The functions defined are: ULONG timer_init(void) Initializes the time out subsystem by allocating the IO requests and opening the timer device. Note that AmiTCP/IP uses functions that are new to version 36 of the timer device, so the code refuses to success with Kickstart 1.3 (or lower). Returns the signal mask to wait for if successful. void timer_deinit(void) Frees all resources used by time out subsystem. void timer_send(void) Sends time out requests allocated by timer_init() tothe timer device. struct timeoutRequest *createTimeoutRequest(TimerCallback_t fun, ULONG seconds, ULONG micros) Creates a new time out request. This can be called only after successful timer_init(). void deleteTimeoutRequest(struct timeoutRequest *tr) Deletes requests created by createTimeoutRequest(). BOOL timer_poll(VOID) Checks if there are any timer requests in the reply port and if there is handles them by calling the handleTimeoutRequest(). Then it sends the request back to the timer device. void handleTimeoutRequest(struct timeoutRequest *tr) Inline function which simply calls the function specified in the time out request. ________________________________ 1See standard Amiga include file devices/timer.h System Manual AmiTCP/IP Section 5.2 95 void sendTimeoutRequest(struct timeoutRequest *tr) Inline function which sends the request to the timer device. See the files kern/amiga_main.c and kern/amiga_time.c for example of the usage. 5.2 Memory Management 5.2.1 Mbufs Mbufs are ported just as they are. Memory is allocatedin small chunks for both mbufs and clusters. The size of the cluster and the number of mbufs to allocate in one chunk are configurable variables, see section 2.4 for summary of the configurable variables in general. Note thatsince data must be copied at the SANA-II interface there is no need to use trailer protocols (whosemain gain is avoidance of that copy) and so the mbuf clusters need notbegin at page boundaries2. This fact lead to the implementation of the clusters where the size of the cluster may be arbitrary (now user configurable) and the reference count of the cluster is stored in a little header before the actual data. One noteon allocating memory for the mbufs: Since the mbuf must be perfectly aligned (i.e. 128 byte mbuf must be 128--aligned), we need to allocate one extra mbuf to be able to align the mbufs in arbitrary memory chunk returned by Exec AllocMem(). The 'canwait' argument of the mbuf functions is ignored by now, more memory will be allocated if limit of maximum memory usage is not hit. This is all right as long as the mbuf allocation functions are not called from interrupts. The only functions in the AmiTCP/IP which may get called from the interrupt code are the SANA-II callback functions m_copy_from_mbuf() and m_copy_to_mbuf()defined in file net/sana2copybuff.c. The function descriptions for the mbufs are on section 4.5. See files sys/mbuf.h and kern/uipc_mbuf.c for the actual implementation. 5.2.2 malloc() & free() Do not call the malloc() and free() functions directly! Since AmiTCP/IP is multi--threaded program these functions are not safe, since they use static data. File sys/malloc.h defines two inline functions to be used instead. They are defined as follows: static inline void * bsd_malloc(unsignedlong size, int type, int flags) This function calls malloc() to allocate memory of size size and type type. The types are defined in sys/malloc.h and are used for bookkeeping purposes. The flags may have either value M_WAITOK or M_NOWAIT. The flags are not used by this implementation, however, but are defined for portability. ________________________________ 2Well, there is no virtual memory in Amiga either. 96 Section 5.3 AmiTCP/IP System Manual static inline void bsd_free(void *addr,int type) Frees memory allocated by bsd_malloc(). The malloc() and free() are made safe by malloc_semaphore, which protects mallocs and frees from collisions. It is obtained before the actual calls to malloc() or free() and released after them. In addition to these functions sys/malloc.h defines macro versions for the most usual usages. Initialization for these functions is done by: BOOL malloc_init(void) Initializes the malloc_semaphore. This must be called early in the initialization process, since bsd_malloc() nor bsd_free() cannot be called before malloc_semaphore is initialized. 5.3 Concurrency Control 5.3.1 Processor Priority Levels In Unix systems the critical sections are mainly protected by raising the processor priority level (i.e. preventing interrupts upto a certain level). This crude way might hurt a real time operating system as the AmigaOS, so it can not be implemented assuch. Besides, the AmiTCP/IP runs as a normal user level process which has no needed privilege to alter the interrupt levels of the processor. The implementation in AmiTCP/IP is twofold; a semaphore is used in the debugging mode and task switch prevention is used in the production version. Using semaphore makes debugging easy as single stepping and tracing is possible while keeping the system alive. The semaphore adds certain overhead which is not acceptablein the production version, so the prevention of the task switches is used. When thepreprocessor symbol DEBUG is defined the spl_semaphore is used. When this semaphore is free the process is at level SPL0 (user level) and when the semaphore is allocated the process is at ``interrupt'' level (SPLSOFTCLOCK, SPLNET or SPLIMP), effectively disallowing anyone else to enter critical section. When the symbol DEBUG is not defined the functions and macrosare defined differently. They manipulate directly the Task Disable Nest Count (TDNextCnt field of the SysBase). This field is normally used by Exec functions Forbid() and Permit() which increment and decrement the value of the field, respectively. Since an assembler macro is provided byCommodore for the Forbid() function, the semantics of thefield cannot change in the future3. The semantics of the TDNestCnt is littleabused by the AmiTCP/IP, however. The value of the spl level in question is directly assigned as the value of the field. This is not visible outside of the AmiTCP/IP, since basically functions which use this field either directly or indirectly (via Forbid()/Perm* *it()) need to return the value of the field asit was when the function was called. This is also the semantics of the usageof the spl functions. ________________________________ 3If it would, then all the codeusing that official macro would break. System Manual AmiTCP/IP Section 5.3 97 The spl_semaphore is initialized by the function BOOL spl_init(void) which is called among the very first functions in the initialization process. The function spl _n(int) (defined in sys/synch.h) is used to alter the priority levels. In the non-debugging mode there are two other functions, too: spl_const(), which isused with a constant (non-zero) argument, and spl_0() which is used to switch to the level 0. In addition, a macro has been defined for each separate level for portability. The macros are as follows: spl0() switches to the normal execution level. splsoftclock() is the level on which timer eventsare executed. splnet() is the level of the network interrupts in UNIX. splimp() is the highest of the priority levels used in the networking code. For example, mbuf functions are executed at this level. These macros return the previous level which may then be set back with splx(int)-macro, which sets the level tothe level given as argument. 5.3.2 Sleeping Facilities Sleeping facility is implemented by kern/kern _synch.c. Processes sleep on a channel, which is the key used to identify sleepers. Usually this is some address which is unique to the calling process. Socket base structure for the sleeping task is linked in the sleep queue before the actual sleep is started. This is how the waking task can find the sleeper to wake up when something happens on the channel the process is sleeping on. The sleepqueue is implemented as a hash table, where the channel value is mapped to an index of a sleep queue with the hash function4 SLEEP_HASH() (defined in kern/kern_synch.c). The actual sleep is implemented by sending a time out request for the time out duration to the timer device. The sleep completes on the time out, a wakeup, a break signal or users specified signal. Note thatall critical resources (e.g. semaphores) must be freed before sleeping, since otherwise the whole networking code hangs. tsleep() does this for you. The functions which implement the sleep system are: BOOL sleep_init(void) This initializes the sleep_semaphore and the sleep queues. Thismust be called before any other functions of this module. int tsleep(struct SocketBase *p, caddr_tchan, char *wmesg, const struct timeval *time_out) ________________________________ 4Actually a macro. 98 Section 5.3 AmiTCP/IP System Manual This function is the function usually called by the processes5 and implements the sleep by using the other functions of this module. The caller goes to sleep for at most the time specified in the struct timeval argument. chan is the channel to sleep on. wmesg is a string which is marked in the socket base (p) as the reason to sleep. Currently no-one ever reads it, though. void wakeup(caddr_t chan) Wakes up any sleepers on channel chan. Searches the sleep queue for entries with key chan and wakes them up by first clearing the key (p_wchan field of the socket base structure) and then signalling the process with the signal of the time out message. The usage of the sleep queues and the p_ fields in the socket base structures are protected with sleep_semaphore, which must be obtained before even reading the sleep queues. Note that since a task in AmigaOS may get signals anytime, the sleeper checks the p_wchan field on reception of thesignal and if it is nonzero it goes to sleep again. void tsleep_send_timeout(struct SocketBase *p, const struct timeval *time_out) First ensures that the message previously sent to the timer device is back. Then sends timer device a time out request for duration specified in time_out if it is not NULL. The requestsent is allocated when the library is opened. void tsleep_abort_timeout(struct SocketBase *p, const struct timeval *time_out) Aborts the time out sent by the tsleep_send_timeout(). This function must be used when the time out must be cancelled (when the sleeper is waken up). This function just sets the timer reply port (timerPort field of the socket base) to the mode in which reception of the message does not cause any action. void tsleep_enter(struct SocketBase *p,caddr_t chan, char *wmesg) Puts the caller on to a sleep queue. int tsleep_main(struct SocketBase *p, ULONG wakemask) Waits for either time out, wakeup, break or user defined action to happen. The sigIntrMask field of the socket base structure defines which signals will cause a break. Return value of EINTR is returned if any of the signals specified in that mask are received. In addition the signals are set back with SetSignal() for the user program to be able to detect them. The wakemask argument specifies a mask for signals which should cause a return from the sleep. In such case the return value ERESTART is returned. ________________________________ 5Only WaitSelect() system calluses sleeping facilities without this function. System Manual AmiTCP/IP Section 5.4 99 On exit the process is guaranteed not to be in the sleep queues any more, but the time out remains active if it is not the reason for return. Return value on wakeup is 0 and on time out EWOULDBLOCK is returned. 5.4 Socket Library Creation Procedure Since a new socket base is created eachtime a different task opens the AmiTCP/IP socket library, the procedureis a bit more complicated than on libraries where the same library baseis returned (See [RKM Libraries 1992 ]). There is, for example, two socket library bases in use. All code discussed here is located in api/amiga _api.c. 5.4.1 Master Library Base This is the library base that is made shown by api_show() (see section 4.10.2). It is placed in Exec's librarylist. This is of type struct Library and contains information that anoutsider can read by scanning through the Exec library list. Information available is version and revision numbers and count of tasks thathave (application) library base open. Master library base has only functions ELL_Open() and ELL_Expunge(). When applications opens the socket library, the Exec calls ELL_Open(). This function creates new application socket bases and increments the reference count of open application library bases. Ifthe calling task has a socket base open already, a new socket base is not created but the reference count of task's socket base isincremented and the base pointer is returned to the caller. This feature has many useful possibilities, for example in intermediate libraries which need to manipulate the sockets of the calling task. ELL_Expunge() does (not) do one task. When it is called, it checks if there is any libraries still open or ifAmiTCP/IP lets this function execute further (in fact, currently thissecond check is sufficient since only AmiTCP/IP can close the library andit doesn't do it until all bases has been closed. The next Remove() is there forfuture reference too). Then, the memory of the master library base is deallocated and NULL is returned (no AmigaDOS seglist tofree). The SIGBREAKF _CTRL_C signal that is sent with the global varianble SB _Expunged set to TRUE notifies api_deinit() function about the fact that now all libraries are closed. 5.4.2 Application Library Bases These are the library bases that are returned to the openers of the socket library. In this base the Open() function is obsolete since all OpenLibrary() calls go through the master socket base. Exec and AmiTCP/IP generated Expunge() calls go also through the master socket base. UL_Close() is the close function for all application library bases. First it decrements the reference countof this base and returns NULL if 100 Section 5.5 AmiTCP/IP System Manual there are still references left (again,NULL informs Exec that there is no AmigaDOS seglist needed to be removed). If thereare no more references to this library base, following steps are taken to remove it from the memory: All socket descriptors still open are closed. The base is removed from the AmiTCP/IP list of open application socket bases. The timer request is deallocated. Then the library base is removed from the memoryand open count of application socket bases is decremented in the master library base. Finally, ELL_Expunge() is called if the open count reached zero and the LIBF_DELEXP flag is set (by a previous ELL_Expunge() call). 5.5 The SocketBase Structure The SocketBase structure is defined in file api/amiga_api.h as follows: struct SocketBase - struct Library libNode; /* "Global" Errno */ WORD errnoSize; /* -- now we are longword aligned -- */ UBYTE * errnoPtr; /* this points to errno */ LONG defErrno; /* Task pointer of owner task */ struct Task * thisTask; /* task priority changes (WORDS so we keep structure longword aligned) */ WORD myPri; /* task's priority just after libcall */ WORD libCallPri; /* task's priority during library call */ /* -- descriptor sets -- */ WORD dTableSize; WORD nextDToSearch; struct socket ** dTable; /* AmiTCP signal masks */ ULONG sigIntrMask; ULONG sigIOMask; ULONG sigUrgMask; /* -- these are used by tsleep()/wakeup() -- */ char * p_wmesg; queue_chain_t p_sleep_link; caddr_t p_wchan; /* event processis awaiting */ struct timerequest * tsleep_timer; struct MsgPort * timerPort; /* -- pointer to select buffer during Select() -- */ struct newselbuf * p_sb; /* -- per process fields used by various'library' functions -- */ /* buffer for inet_ntoa */ char inet_ntoa[20]; /* xxx.xxx.xxx.xxx"0 */ /* pointers for data buffers that MAY beused */ struct DataBuffer selitems; struct DataBuffer hostents; struct DataBuffer netents; struct DataBuffer protoents; struct DataBuffer servents; "; System Manual AmiTCP/IP Section 5.6 101 libNode is a normal library base structure and is used by the system. Since in this implementation each openergets a task specific library base, AmiTCP/IP links all ``user librarybases'' together using Node field of libNode. When socket library function encounters an error, it saves the value of the error to the memory location addressed by errnoPtr. errnoSize specifies the size of the variable pointed by the errnoPtr. By default errnoPtr points to the defErrno but canbe changed to point any memory location -- usually to the global errnovariable in the context of the user task. In entryof each bsdsocket.library function call, value of thisTask is compared to the task pointer of callingtask to make sure right task is calling the function. This variable is also used to find library base of some executing task. When taskis executing system calls in bsdsocket.library, its process priority is changed to the same as thatof the AmiTCP/IP task in order not to hold semaphores and block the whole network system. If some higher priority process becomes active and a lower priority task is holding some vital semaphore of the AmiTCP/IP then the precess cannot continue to run. The manipulation of the process priorities uses myPri and libCallPri fields of the socket base. dTableSize is the number of current maximum limit of socket descriptors. dTable is the descriptor table containingpointers to socket structures, i.e. sockets. nextDToSearch makes searching of free socket descriptor faster6. sigIntrMask is a task specific mask of the signals which should break the Wait() call in the tsleep_main(). Reception of such signals causes the system calls to return -1 and the error code pointed by errnoPtr to be set to EINTR. sigIOMask field specifies the signals to send when asynchronous notification is requested. Signals specified in sigUrgMask are sent when out of band data is received. These masks implement the functionality of the SIGIO and SIGURG signals of the Unix systems, respectively. All these masks can be set with the SetSocketSignals() API call. The default mask for the sigIntrMask specifies the signal for the ctrl-C, other two are zero by default. The nextgroup of variables are used by tsleep() and wakeup(). p_wmesg points to a string telling the reason why task is sleeping. p _sleep_link is used to chain library bases in the sleep queues. Waiting channel key is hold in variable p_wchan and data handling time outs in variables tsleep_timer and timerPort (more about this in section 5.3.2 on page 97). WaitSelect() inserts one selitem on each socket it wants event information of. p_sb points to a newselbuf that contains these items. Some APIfunctions in the original environment use static buffers to store their output. As a shared library cannot use static buffers (to be re-entrant), the buffers must be allocated dynamically. The SocketBase structure has space for the output buffer for the Inet_Ntoa() and pointers for other needed buffers. These buffers are allocated only when needed. ________________________________ 6Semantics of allocating lowestfree socket descriptor is preserved. 102 Section 5.6 AmiTCP/IP System Manual 5.6 The Application Program Interface Most of API code is original NET/2 codetaken from BSDSS7. BSDSS mutexes are replaced by Amiga semaphores and struct proc references are changed to references to our socket library base8. Many functions used copyin() and copyout() to copy data around. Those functions copy data between system and user space (different virtual memory mappings) in original BSD Unix system. It is also possible that those functions will fail e.g. if user tries to reference illegal memory locations. In AmiTCP/IP system copyin() and copyout()functions are replaced with bcopy(), arguments are thesame but the bcopy() never fails. Therefore some obsolete checks are removed fromthe code. 5.6.1 HowAPI Functions Are Ported Most functions in our API are ported from BSD Unix system calls. BSD Unix system call interface calls the actual function with three arguments. First is user process structure pointer. Second contains the given arguments and is locally named asuap structure. Third argument is a pointer to the return value. The function returns error value or 0 if no error occurred. The socket() function is a good example of this: socket(p, uap, retval) struct proc *p; registerstruct args - int domain; int type; int protocol; " *uap; int *retval; The system function interface maps directly to Amiga shared library. Since every task has socket library baseof its own, Unix process pointer matches to library base pointer given inregister A69 . uap argumentsare passed in registers normally. Return value is returnedin the register D0 (as any standard C compiler does). So the *retval was changed to a local variable retval and removed, whennot needed. The returned value is -1 on error, in which case the errnois also set to indicate the error (see file sys/errno.h for list of errorcodes), or retval, or 0 if no other return value is needed. To emulate Unix system call interface, each function first obtains the syscall_semaphore (why,see section 4.10.2 on page 90) and while this task is holding the semaphore, no othertask can continue to execute the ________________________________ 7BSDSS networking code is almost completely the same. 8See 5.5. 9All Amiga shared libraries expect them to be called relative to register A6 System Manual AmiTCP/IP Section 5.7 103 library code10. This means that every system call function needs to release syscall_semaphore before returning. To accomplish this, each return inside the function is changed togoto Return; and at label Return: is code that releases the syscall_semaphore11 . The default modifications were: changing file descriptor tables and pointers to socket tables and pointers,respectively, removing usage of struct fileops function pointers -- replacing them with direct socket functions, changing copyin() and copyout() functions to bcopy()s -- no more error checking here needed, and last, changing parameters on tsleep() calls. 5.6.2 APIFunctions Which Needed More Modifications IoctlSocket() (Former ioctl()): Non-socket stuff removed, and ioctl code from soo_ioctl() inserted. WaitSelect() (Former select()) usedto count remaining time out time if tsleep() returned accidentally too early. AmiTCP/IP uses the timer device for its time outs and the time out request is aborted only when it is needed again, so there is no need to send new time out requests (and to calculate their time out durations). tsleep() is broken apart into pieces so that the time out request is sent only once. CloseSocket() Decreases socket's referencecount and calls soclose() to kill the socket if it becomes zero. socket() and accept() Added initialization of so_refcnt. fdAlloc() doesn't bind fd to socket. It is done explicitly. Resolver functions used to allocate huge amounts of stack. Now memory is allocated dynamically from the head using bsd_malloc. 5.7 Changes in Functions Below API Level Functions that API functions call are mostly functions that use struct socket type arguments, possibly having some other arguments too. In most cases no modifications were needed. There was some modifications, like parameters for tsleep() call, which hadto be changed throughout the code. 5.7.1 Other Changes selscan() calls soo _select() directly, and uses socket pointer instead of file pointer. soo_select() also uses socket pointer instead of file pointer. ________________________________ 10Not all functions require obtaining syscall_semaphore so those can continue to run. 11syscall_semaphore is also freed when library function does tsleep(). 104 Section 5.8 AmiTCP/IP System Manual socreate() allows allsockets to be privileged. This means that user can obtain raw sockets and use normally privileged port numbers. sosend() uses uioread() instead of the original uiomove(). sblock() and sbwait() are called with library base pointer as the second argument. soreceive() uses uiowrite() instead of the original uiomove(). See above about the modification of call sblock() and sbwait(). sorflush() calls sblock() with base pointer argument as NULL. sosetopt() and sogetopt() : type of so_linger and so_timeo fields in socket structure is changed from short int to struct timeval. Manipulation of these data is changed accordingly. sbwait() takes socket base pointer as second argument. It is then passed to tsleep() (see section 5.3.2 on page 97). sblock() and sb_lock(): sblock() is a macro that calls sb_lock(). Both take socket base pointer as second argument. sblock() forwards that pointer directly to sb_lock() which, again, passes it to the tsleep(). 5.8 Agnet.device We used the agnet.device, an SANA-II test device, to test and develop the AmiTCP/IP network code. The usage and features of theagnet.device are described in the section 1.7.1, page 10. SANA-II is an standard network device driver interface for the Amiga (see [SANA-II 1992 add ]). It is an extension to the normal device interface, which is described in the [RKM Libs & Devs 1989 ]. Device drivers are accessed by their name fromthe system list. Drivers may be loaded dynamically from the disk, if they are not currently in the main memory. There may be several units sharing common driver code so each (network) device is specified by an unitnumber and the device driver name. We wrotethe agnet.device using the SLIP driver which Commodore has provided as the example code for SANA-IIdrivers. However, there was some problems with the supplied code. First, the SLIPdriver code obviously follows an obsolete SANA-II draft. There was some modifications in the final standard e.g. in the eventhandling and multicast addressing. The provided example code was also very fragile, it did not get compiled as such with the newer SAS C version 6. Codedepended on some features of the SAS C 5.10. For example, it always expected to find device base pointer in address registerA6. 5.8.1 IO Commands There is a detailed description of SANA-II device commands and functions in [SANA-II 1992 add ]. The following IO commands are implemented in agnet.device: System Manual AmiTCP/IP Section 5.8 105 CMD_CLEAR This standard command should return IOERR_NOCMD when issued to SANA-II device. CMD_INVALID This standard command should return IOERR_NOCMD. CMD_READ Get the next packet available of the requested packet type. The data returned (via a call to the requester-provided CopyToBuffer() function) is the Data Link Layer packet data only. Raw packets are not supported. CMD_RESET This standard command should return IOERR_NOCMD when issued to SANA-II device. CMD_START This standard command should return IOERR_NOCMD when issued to SANA-II device. CMD_STOP This standard command should return IOERR_NOCMD when issued to SANA-II device. CMD_UPDATE This standard command should return IOERR_NOCMD when issued to SANA-II device. CMD_WRITE Send packet to the network. Raw packets are not supported. Sending packet with a broadcast hardware address is not supported. S2_BROADCAST Broadcast a packet to the network. Raw packets are not supported. S2_CONFIGINTERFACE Configure the interface. The address field will be set depending on the specified hardware type. S2_DEVICEQUERY Report the statistical information about the device. S2_GETGLOBALSTATS Report accumulated statistics as defined in struct Sana2Devicestats. S2_GETSTATIONADDRESS Report the ``hardware'' address for the unit. Before the configuration, the current hardware address has all bits set. The default hardware address is not stored anywhere. S2_GETTYPESTATS Report accumulated statistics of the tracked packets. S2_OFFLINE Remove interface from service. Flush all queued IO requests. 106 Section 5.8 AmiTCP/IP System Manual S2_ONEVENT Return when specified event(s) occur(s). S2_ONLINE Put the interface back in service. This command resets the unit statistics. S2_READORPHAN If there is no pending CMD_READ request with the type of the received packet, the packet is given to first pending S2_READORPHAN request. The data returned (via a call to the requester-provided CopyToBuffer function) is the Data Link Layer packet data only. Raw packets are not supported. S2_TRACKTYPE Start tracking of the specified packet type packets. S2_UNTRACKTYPE Stop tracking of the specified packet type packets. Uninplemeted IO Commands These SANA-II device commands are not supported. CMD_FLUSH This standard command returns IOERR_NOCMD when issued to the agnet.device. S2_ADDMULTICASTADDRESS This SANA-II command is not supported. It returns IOERR_NOCMD. S2_DELMULTICASTADDRESS This SANA-II command is not supported. It returns IOERR_NOCMD. S2_GETSPECIALSTATS This SANA-II command is not fully supported. It returns an empty Sana2SpecialStat structure. This command should report accumulated driver specific statistics. This includes ethernet ``retries''. S2_MULTICAST This SANA-II command is not supported. It returns IOERR_NOCMD. 5.8.2 Initialization Procedure agnet.device must be started as a DOS process by the Run command. The dynamic loading is not yet implemented. Its own startup module, init.c, opens needed libraries, initializes device base and calls the main() function. Main function opens timer.device and initializes the ARexx port; if initialization was successful,it adds the device base to the system list. In the main loop the device task waits for three different events: user or Expunge() generated break signal(SIGF _BREAK_F), ARexx messages or user IO request messages. System Manual AmiTCP/IP Section 5.8 107 5.8.3 TheDevice Interface Functions The device interface contains 6 standardlibrary calls in the device base. The device may be opened or closed, the IO requests may be initiated or aborted and the system mayreclaim storage allocated by the device driver. These library calls are not normally executed directly by the user code but instead higher level convenience functions in the Exec. The device base library calls are made in the context of the caller, so some synchronous IO commands may be executedlike library calls without message passing overhead (quick IO). The synopsis of the functions specifies the registers where the call parameters are passed (REG(rn)). Opening an Unit An IO device is opened by the Exec function call OpenDevice(). When Exec has found the named device driver in thesystem list it calls the special DevOpen() function from the device base. DevOpen() function has following synopsis: ULONG ASM DevOpen(REG(a1) struct IOSana2Req *ios2, REG(d0) ULONG unit, REG(d1) ULONG flags) The device open function tries to allocate and initialize various resources for the specified unit if the unit does not already exist. The initialization routine InitUnit() is called; if it returns an unit structure, a private buffermanagement structure is filled from the user provided tag list. The user supplied IO request is filled with appropriate values. The promiscuous or exclusive modes are not supported. struct AgnetDevUnit *InitUnit(ULONG); The initialization routine allocates an unit structure and then calls the configuration routine ReadConfig(). If the configuration file was read and interpreted without errors the lists and locks in the unit structure is initialized. The unit is then put online and unit structure is added to the device base. BOOL ReadConfig(struct AgnetDevUnit *adu) The configuration routine attempts to read in the configuration file for the given unit. It strips the comments out of the file and provides the file as a single line to the parsing routine ParseConfig(). If there is no configuration file, the parsing routine is called with an empty line. Closing an Unit The accessed unit is closed after use with the Exec CloseDevice() function. It runs the DevClose() function from the device base. The call has the following synopsis: 108 Section 5.8 AmiTCP/IP System Manual BPTR ASM DevClose(REG(a1) struct IOSana2Req *ios2) The device close function calls the unit close function. If the device has been asked to Expunge() itself, the close function sends an appropriate signal to the device task. The device task then performs the postponed expunge function. The DevOpen() and DevClose() calls are executed while Forbid()'den unless the code explicitly Wait()'s. Initiating an IO Request The DevBeginIO function from the devicebase is called to initiate an IO command. This call is made in the context of the requesting task (task calling Exec functions DoIO() or SendIO()). The DevBeginIO() call has the following synopsis: VOID ASM DevBeginIO(REG(a1) struct IOSana2Req *ios2) The IO request is sent to the device task for dispatching and execution. Currently all IO requests are executed in the context of the device task, i.e. no quick IO is supported. Aborting an IO Request Some IO requests may be aborted before they are completed by Exec function call AbortIO(). The aborting function has thefollowing synopsis: VOID ASM DevAbortIO(REG(a1) struct IOSana2Req *ios2) Currently only the CMD_READ, S2_READORPHAN, CMD_WRITE, S2_BROADCAST, and S2_ONEVENT IO commands may be aborted. Other IO commands are executed in an atomic way and can not be aborted reliably. Expunging the Device The system may reclaim the storage allocated by the device driver by calling the DevExpunge() function. Memory reclaiming is normally done in a low memory situation or after a user requested memory flush. The expunging function has the following synopsis: VOID ASM DevExpunge(VOID) The DevExpunge() may not Wait() because it may be called from the Exec function AllocMem() protected by Forbid()/Permit() pair. The DevExpunge() function call currently signals the device task, which performs the actual expunging by calling the DoExpunge(). Each unit structure is expunged by the function ExpungeUnit(). System Manual AmiTCP/IP Section 5.8 109 5.8.4 Packet Delivery The packet sent to the pseudo network may be delayed or mutated randomly. A special structure (struct DelayRequest) stores the packet type and data during the ``transmit delay''. The delay is implemented by sending this structure as a the timer IO request to the timer device. The packet transmit functions are as follows: VOID WritePacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2) The function checks that the unit is on line and checks for the legal data length. Then the routine adds the given IO request (CMD_WRITE, S2_BROADCAST) into send queue. If there is a free delay request the SendPacket() is called immediately. VOID SendPacket(struct DelayRequest *delayed) The SendPacket() function takes an empty DelayRequest structure as its argument. First, it gets a send IO request from the send queue. The request is immediately returned if the packet is ``lost'' during transmit. This is repeated until a packet is found which is not to be lost. The packet data, type, length, transmission type and the address of the sender are then copied into the DelayRequest structure. If needed, bit errors are made into packet data. If there is specified delay for the unit, the request is sent to the timer device. After the delay the timer device returns request into the device port and it is dispatched by the ReceivePacket() function. If there is no delay, the ReceivePacket() is called immediately. VOID ReadPacket(struct AgnetDevUnit *adu, struct IOSana2Req *ios2) The function checks that unit is online, then adds the read request into an appropriate queue. VOID ReceivePacket(struct DelayRequest *delayed) The ReceivePacket() function calls the DoReceive() function on each unit the packet is destined. It then handles the DelayRequest structure back to the SendPacket() function. VOID DoReceive(struct AgnetDevUnit *adu,struct DelayRequest *delayed) The DoReceive() function tries to find a receive IO message waiting for this particular type packets. If none is found, the first S2_READORPHAN IO request is selected. The CopyBack() function sets the appropriate IO request return values and copies the packet data to the receive buffer. 5.8.5 Arexx Interface The ARexx interface is implemented by using the SimpleRexx package provided in Commodore Native Developer Update 2.0 kit. The Arexx commands executed by the agnet.device are described in section 1.7.1, page 12. 110 Section .0 AmiTCP/IP System Manual LONG ParseRexx(UBYTE *arg, UBYTE **errstr, UBYTE **result) The ARexx command string is copied into a buffer and passed to the parser function ParseRexx(). The parser allocates a DOS struct RDArgs structure for the ARexx command. This structure holds the information for the DOS parsing functions. The first keyword in the string is read and tokenized by the DOS functions ReadItem() and FindArg(). The rest of the line is parsed according this token. In the case of Query and Unit commands the unit number is read from the command line and the rest of the line is passed to the appropriate functions. LONG ParseConfig(struct AgnetDevUnit *adu, struct RDArgs *rdargs, STRPTR *errormessage) The command line stored into the RDArgs structure is parsed by the DOS function ReadArgs(). The parsed configuration information are then gathered, its legality is checked and it is stored into the unit structure. LONG ParseQuery(struct AgnetDevUnit *adu, struct RDArgs *rdargs, STRPTR *errstr, STRPTR *result) Like the ParseConfig(), ParseQuery() parses the command line by the ReadArgs(). It then fills the reply buffer by the requested configuration parameter values and then makes an ARexx string out of the buffer. This string is then returned to the ARexx process.