With reference to the OSI/ISO Networking Reference Model, cnet provides the Application and Physical layers. Your protocols are required to ``fill-in'' any necessary internal layers and, in particular, to overcome the corrupted and lost frames that cnet's Physical Layer randomly introduces.
When running under the X-window system, cnet provides an intuitive graphical representation of the network under execution and permits a number of attributes of the network to be modified while the simulation is running. cnet also runs on standard ASCII terminals (rather less visually) if you find a shortage of workstations or X-terminals. You do not need to write any windowing code in your protocols.
Network protocols must be written in ANSI-C and are executed by cnet itself - not interpreted. Because cnet must dynamically link compiled versions of your protocols at run-time, cnet performs all necessary compilation and linking. You do not compile or link your protocols yourself, nor use make to do it for you. Invoking cnet with a valid topology file (described later) will perform all necessary compilation and linking before commencing the simulation.
Links are numbered within each node from 0 to the number of physical links that the node has, with 0 representing the LOOPBACK link (which you'll probably never require). The first ``real'' link is number 1 and every node will have a link number 1.
Perhaps surprisingly, the nodes initially have very little knowledge of the network. Nodes do not know how many other nodes there are, what the other nodes are called, nor the attributes of any nodes or links other than their own. All inter-node communication necessary to learn this information must traverse the Physical Layer.
/* A simple 2 node network topology */ compile = "stopandwait.c" messagerate = 500ms, propagationdelay = 700ms, probframecorrupt = 3 host Perth { x=100, y=100 messagerate = 1000ms, link to Melbourne } host Melbourne { east of Perth link to Perth { probframeloss = 2 } }Node and link attributes declared before any nodes are considered global attributes - these will be the defaults unless redefined locally within a node or link definition. Local attributes are declared in a new ``block'', by opening a curly bracket (as in C). In the above topology, the default message rate (the rate at which the Application Layer will generate a new message for delivery) is 500ms. This becomes the default messagerate for all nodes, but node Perth later declares its own (local) messagerate as 1000ms.
The attribute compile indicates which ANSI-C source files are to be compiled and executed by cnet. Here, the source code in the single file stopandwait.c will be executed by both Perth and Melbourne. Each node will have its own copy of all variables declared in the file stopandwait.c (globals, static globals, locals and static locals).
IMPORTANT: Because cnet must dynamically link in your compiled source code at run-time, cnet must compile and link your source code itself. While using cnet you do not compile and link your own files, nor do you use make to do it for you. You simply invoke cnet with the name of the required topology file as an argument and cnet will compile and link your source code for you (as necessary). If you are a little concerned about this ``black-magic'', invoke cnet with the -d option to have it report what it is doing. For example, cnet -d TOPOLOGY_FILE .
The attributes probframecorrupt and probframeloss each specify a uniform distribution, with their value being the log2 of the chance of failure (yes, this is ugly). In this topology, the global probframecorrupt attribute declares that a frame will be corrupted with probability 1:8 (1:23) and the link from Melbourne to Perth will lose (on average) every fourth frame. A probability of 0 (the default) means that no errors will be introduced.
Events occur when a node reboots, the Application Layer has a message for delivery, the Physical Layer receives a frame on a link, a timer event expires, a debugging button (under X-windows) is selected, and a node is (politely) shutdown (no event is delivered if a node pauses, crashes or suffers a hardware failure). These last few events only occur in simulations designed to address the highest layers of the OSI model - so do not worry about them for now.
Each node is initially rebooted by calling your function reboot_node (this is the only function that you must provide). reboot_node is an example of an event-handling function. All such functions are called by cnet with three parameters, the first is the type of event (one of the cnet Event enumerated values), the second is a unique timestamp (described later) and the third, some user-specified data (typically 0 and ignored). The purpose of calling reboot_node is to give your protocols chance to allocate any necessary dynamic memory, initialize variables, inform cnet in which events your protocols are interested, and which functions cnet should call when these events occur.
Consider the following protocol skeleton. Like all cnet protocol files, the standard cnet header file is first included. This contains all definitions and function prototypes necessary for your protocols. Here the reboot_node function informs cnet that when the Application Layer has a message for delivery to another host (because EV_APPLICATIONREADY occurs) cnet should call the function appl_ready which, presumably, will read the new message from the Application Layer and thus commence the delivery process.
#include [cnet.h] void reboot_node(cnetEvent ev, cnetTimestamp ts, cnetData data) { void appl_ready(cnetEvent, cnetTimestamp, cnetData); ... result = cnet_set_handler(EV_APPLICATIONREADY, appl_ready, 0); ... } void appl_ready(cnetEvent ev, cnetTimestamp ts, cnetData data) { ... result = cnet_read_application( ... ); ... }IMPORTANT: Your event-handling functions must execute to their completion, that is they must perform their actions and then simply return. Event-handling functions are of type void - that is, they do not return a value. If your event-handling functions do not return, the whole simulation will block and cnet must be interrupted via the invoking xterm.
cnet also supports 10 timer event queues providing a call-back mechanism for your protocol code. For example, the event EV_TIMER1 may be requested to be ``raised'' in 5000ms, and cnet will call your EV_TIMER1 event-handler in 5 seconds time. Timers are referenced via unique values termed timestamps. When a timer expires, the event-handler for the corresponding event is invoked with the event and the unique timestamp as parameters. Timers may be cancelled prematurely to prevent them expiring, though they are automatically cancelled as a result of their handler being invoked.
Each node has access to its own CnetNodeinfo structure describing the node's attributes. This structure is best considered read-only as its contents are ``refreshed'' as each node is scheduled for execution.
typedef struct { cnetNodetype nodetype; /* Either a NT_HOST or a NT_ROUTER */ int nodenumber; /* Ranging from 0.._NNODES-1 */ cnetAddr address; /* Possibly different to the nodenumber */ char *nodename; int nlinks; /* Ranging from 0(=LOOPBACK) .. nlinks */ int minmessagesize; /* min size (in bytes) of msgs generated */ int maxmessagesize; /* max size (in bytes) of msgs generated */ int messagerate; /* rate of msg generation (in ms) */ long time_in_ms; /* a monotonically increasing clock */ struct { long sec; long msec; } time_of_day; /* a reflection of the wall-clock time */ }cnetNodeinfo;CnetNodeinfo nodeinfo; When cnet informs your protocols that the Application Layer has a message for delivery, your protocols will read the message into a buffer supplied by you. You must first indicate the maximum message size that you are willing to receive. A successful read will then ``fill-in'' the address of the message's destination node and the actual length of the message. Your protocols are simply presented with ``a lump of bytes'' which they must deliver to other Application Layers. The message is to be considered as opaque data, its contents are immaterial, though suffice to say that there is sufficient ``good'' information in the message for cnet to diagnose most protocol errors for you. A typical sequence is:
char msgbuffer[ MAX_MESSAGE_SIZE ]; cnetAddr destaddr; int length; length = sizeof(msgbuffer); result = cnet_read_application(&destaddr, msgbuffer, &length); ... /* prepare for message transmission */Your protocols will typically need to restrict, or throttle, the generation of messages for certain destination nodes. This may be achieved using the functions CNET_enable_application and CNET_disable_application which each accept a single parameter indicating which destination address to throttle.
When the message reaches the correct destination node, it may be written to the Application Layer:
result = CNET_write_application(msgbuffer, &length);
Each node has access to its own array of read-only CnetLinkinfo structures:
typedef struct { int bandwidth; /* in bits per second */ int propagationdelay; /* in ms */ int transmitbufsize; /* in bytes */ int costperbyte; /* in cents(?) */ int costperframe; /* in cents(?) */ } CnetLinkinfo;
CnetLinkinfo *linkinfo; /* linkinfo[0]..linkinfo[nodeinfo.nlinks] */To find the propagation delay of the first ``real'' link in a two node simulation, each node would simply access linkinfo[1].propagationdelay.
When your protocols wish to transmit a data frame along a link, they write that frame to the Physical Layer. On calling the CNET_write_physical function, you indicate the length of the frame to be written and on return CNET_write_physical indicates how many bytes were accepted. A typical sequence for a two node network is:
char myframe[ MAX_MESSAGE_SIZE + MY_OVERHEAD ]; int framelength; ... /* prepare frame contents for transmission */ framelength = ... ; result = CNET_write_physical(1, myframe, &framelength);When cnet informs the destination node that a frame has arrived, the handler for EV_PHYSICALREADY should read that frame. On return from a successful call to cnet_read_physical, your protocol is informed on which link the frame arrived and how long it was. Of course, in a simple two node network all frames will arrive on link number 1.
char myframe[8*K]; int link, framelength; framelength = sizeof(myframe); result = CNET_read_physical(&link, myframe, &framelength); ... /* process frame contents */
Selecting any node (with the left mouse button) displays the node's stdout window and permits some local node attributes to be modified. Similarly, selecting any link (with the left mouse button) displays that link's window via which local link attributes may be modified. Selecting a link means clicking ``close enough'' to the source node of that link. Although the links are bidirectional, they may have different attributes in each direction.
The global node and link attributes may also be changed by selecting their respective buttons from the primary cnet frame. Changing these attributes will affect every node and link unless local attributes have been specified.
Each node's output may also be mirrored in individual disk files. As an example, invoking cnet with the option -o output will create and write individual output files, such as output.Perth and output.Melbourn.
It is very important, from the beginning, that you check the return value of every cnet function.
cnet also provides five debugging events, EV_DEBUG1..EV_DEBUG5, to help with debugging protocols under X-windows. By providing a handler for any of these events and specifying a string for the debugging button (so that it appears), you can call sections of your source code to report the value of certain data structures.
Protocols that appear correct may also be debugged using event tracing to monitor events and function invocations (ala trace(1) and truss(1)). Tracing may be requested for all, or individual nodes. Tracing appears on cnet's stderr stream. An example follows:
enter Perth.EV_TIMER1 @102310ms cnet_start_timer(EV_TIMER1, msec=100) = 637513505 cnet_write_physical(link=1,0xf7ffdaa0,*len=1059) = 0 (len=1059) cnet_stop_timer(ts=637513504) = 0 leave Perth.EV_TIMER1 enter Melbourne.EV_APPLICATIONREADY @102360ms cnet_read_application(0xf7ffdb38,0xf7ffdb44,*len=4096) = 0 (dest=3,len=336) cnet_disable_application(dest=3) = 0 leave Melbourne.EV_APPLICATIONREADY enter Perth.EV_PHYSICALREADY @102390ms cnet_read_physical(0xf7ffdb18,0xf7ffdb20,*len=4096) = 0 (link=1,len=16) cnet_stop_timer(ts=637513485) = 0 leave Perth.EV_PHYSICALREADYWhen an event of interest occurs in a node, the node name and event are reported just before cnet calls the corresponding event- handler. All cnet functions called while in the handler are also reported (indented) and, if possible, their calling arguments described. Note that some arguments will be pointers and cnet can only describe these as hexadecimal values and cannot interpret their contents. Any reference parameters that are modified by cnet are also reported after each function's return value. Unfortunately, there is no source-level debugging :-(
2)The files CLICK and click.c demonstrate the use of cnet's debugging buttons and the Physical Layer (the Application Layer is not used in this example).
3)The files KEYBOARD and keyboard.c demonstrate the use of cnet's EV_KEYBOARD event. With the mouse in a node's output window, you may send a line of input from the keyboard (and write a command interpreter or an interactive file transfer language and supporting protocol!).