The ONL Tutorial

Tutorial >> More Plugins TOC

Delay Packets

The pdelay plugin shows how a plugin can queue a packet for a fixed period of time (its delay) and then forward the packet when its delay has expired. The forwarding decision is made when a new packet arrives (and pdelay_handle_packet is called), and when a scheduled callback is made (and pdelay_callback is called). The periodic callback is registered with the kernel by the plugin as part of its initialization and is called after the SPC's hardclock routine runs when its period has expired.

Preliminaries

Several library functions and macros are used to deal with the queue, time, interrupts, callbacks and packet forwarding:

These functions are summarized in Summary_Information/SPC_Macros_and_Functions.

Although the delay behavior is relatively simple, the developer must deal with several details of kernel programming. First, the plugin must interact with the SPC's hardclock routine. Second, it must ensure that the use of kernel resources are synchronized properly.

The SPC's hardclock routine is called at a fixed frequency to perform periodic services. The default period is 500 microseconds. The pdelay plugin registers the pdelay_callback function with the hardclock routine during plugin initialization.

The plugin works with two queues: 1) the queue of incoming packets (usually one packet), and 2) the queue holding delayed packets. Packets are placed on a queue by the pdelay_handle_packet function and are dequeued by both the pdelay_handle_packet and the pdelay_callback functions. In order to ensure consistent, uninterrupted behavior, these two routines disable all interrupts using the PLUGIN_SPLCLOCK_FCT function while they work and then reenable the interrupts using the PLUGIN_SPLX_FCT function right before they return.

The queue processing logic looks like this (pseudo-code form):

	pdelay_init_class( ):		// called when plugin is loaded
		Set the callback period to cbPeriod;
		global_pdelay_inst_ptr = NULL;
		Register pdelay_callback with kernel;
	pdelay_create_instance( ):
		Disable interrupts;
		Initialize instance variables;
		global_pdelay_inst_ptr = myinst;
		Enable interrupts;
	pdelay_fwdpkts(cur_time, myinst, caller):	// fwd expired delays
		while (first queued buffer has an expired delay) {
		  Remove first buffer from delay queue and forward packet;
		}
	pdelay_handle_packet(this, bufferList):	// handle incoming pkts
		Disable interrupts;
		Remove buffer from bufferList and insert into delay queue;
		Forward packets with expired delays;
		Enable interrupts;
	pdelay_callback( ):	// run every cbPeriod/500 clock ticks
		if (global_pdelay_inst_ptr != NULL) {
		  if (there is no plugin instance)	return;
		  Disable interrupts;
		  Forward packets with expired delays;
		  Enable interrupts;
		}
In the above pseudo-code, I have removed all logic having to do with packet accounting and debugging.

The queue processing logic has the following features:

We now describe the code that implements this logic.

Instance Variables and the pdelay.h Header File

The pdelay.h code below shows the key constants and the instance state structure and omits the boiler plate code which includes function prototypes:

	#define DELAY_TIME	50	// Initial delay in msec
	static int cbPeriod = 500;	// callback period in usec (500 usec)
	struct pdelay_instance {
	  struct rp_instance rootinstance;
	  HDRQ_t qhead;			// addr of 1st pkt buffer in delay queue
	  int	qlen;			// #pkts in delay queue
	  int	pkt_count;		// #pkts handled by instance
	  int	drop_count;		// #pkts dropped by instance
	  int	fwd_count;		// #pkts forwarded by instance
	  int	max_qlen;		// maximum queue length
	  int	earliest_depart_time;	// earliest time nxt pkt should be sent (msec)
	  int	delay_time;		// amount to delay (msec)
	};
The default delay (DELAY_TIME) is 50 msec, and the default callback period (cbPeriod) is 500 usec. Most of the instance variables are for packet accounting and will not be discussed. qhead is the address of the first packet buffer and is NULL when the queue is empty. When the queue is non-empty, earliest_depart_time is the expiration time of the first packet buffer; i.e., when that packet should be forwarded. The time is derived by adding the delay to the arrival time of the packet; i.e., the time when pdelay_handle_packet gets the packet on its packet list. delay_time is the current plugin delay and is initialized to DELAY_TIME. A user can change the delay by sending the plugin a message of type 2 through the RLI.

The pdelay_handle_packet Function

pdelay_handle_packet moves the packet buffer from its queue of incoming packet buffers bufferList to its delay queue and then removes and forwards packet buffers that have expired from the delay queue. Both the queues are doubly-linked lists that are defined using NetBSD's tail queue facility (see the NetBSD queue(3) manual page). The ONL has simplified the interface functions to the standard interfaces for manipulating queues cited earlier; i.e., msr_firstBuffer, msr_removeBuffer, msr_addBuffer and msr_initBuffer. Now for a tour through the main processing functions starting with the pdelay_handle_packet function.
 1	void pdelay_handle_packet(
 2	  struct rp_instance *this,		// points to instance structure
 3	  void *bufferList)			// points to list of pkt buffers
 4	{
 5	  struct pdelay_instance *inst	= (struct pdelay_instance *)this;
 6	  u_int32_t		curr_time;	// Current time in ms
 7	  struct msr_bufhdr_t	*buffer;	// incoming pkt buffer
 8	  int			s;
 9	
10	  s = PLUGIN_SPLCLOCK_FCT();
11	  buffer = msr_firstBuffer(bufferList);
12	  msr_removeBuffer(bufferList, buffer);		// rm pkt from pkt list
13	  inst->pkt_count++;
14	
15	  curr_time = PLUGIN_CPU_CLOCK_1MSEC_FCT();
16	  buffer->time_fwd = curr_time + inst->delay_time;	// when pkt should go out
17	
18	  msr_addBuffer(&inst->qhead, buffer);		// queue pkt
19	  inst->qlen++;
20	  if (inst->max_qlen < inst->qlen)	inst->max_qlen = inst->qlen;
21	  if (inst->qlen == 1) {
22	    inst->earliest_depart_time = curr_time + inst->delay_time;
23	  }
24	
25	  pdelay_fwdpkts (curr_time, inst, 0);
26	  PLUGIN_SPLX_FCT(s);
27	  return;
28	}
The pdelay_handle_packet code tour:

The pdelay_fwdpkt Function

The pdelay_fwdpkt function is called from pdelay_handle_packet and pdelay_callback to forward packets whose delay has expired. It should run only when interrupts have been disabled to prevent either an arriving packet or callback from modifying the delay queue while it is running.

 1  void pdelay_fwdpkts (
 2    u_int32_t	curr_time,		//
 3    struct pdelay_instance *myinst,	// instance addr
 4    int	caller			// 0: called by pdelay_handle_packet
 5  					// 1: called by pdelay_callback
 6  ) {
 7    struct msr_bufhdr_t	*buffer;	// pkt buffer
 8  
 9    while ( (myinst->qlen > 0) && (curr_time >= myinst->earliest_depart_time) ) {
10      buffer = msr_firstBuffer(&myinst->qhead);
11      msr_removeBuffer(&myinst->qhead, buffer);
12      if (PLUGIN_IP_FWD_FCT(buffer) == 0)	myinst->fwd_count++;
13      myinst->qlen--;
14      if (myinst->qlen > 0) {		// check next packet
15        buffer = msr_firstBuffer(&myinst->qhead);
16        myinst->earliest_depart_time = buffer->time_fwd;
17      }
18    }
19  }
The pdelay_fwdpkt code tour:

The pdelay_callback Function

The pdelay_callback function is similar to the pdelay_handle_packet function in that it will forward packets with expired delays. But it is different in that it is called periodically rather than as a result of an arriving packet.

 1  void pdelay_callback(void) {
 2    struct pdelay_instance	*myinst;
 3    u_int32_t			curr_time;
 4    int			s;
 5  
 6    if (global_pdelay_inst_ptr == NULL)	return;
 7    myinst = (struct pdelay_instance *) global_pdelay_inst_ptr;
 8    if (myinst == NULL)	return;
 9  
10    if (myinst->qlen > 0) {	// Get current time; check for expired delays
11      s = PLUGIN_SPLCLOCK_FCT();	// Block clock interrupts
12      curr_time = PLUGIN_CPU_CLOCK_1MSEC_FCT();
13      pdelay_fwdpkts (1, curr_time, myinst);
14      PLUGIN_SPLX_FCT(s); 
15    }
16  }
The pdelay_callback code tour:

The pdelay_init_class Function

The pdelay_init_class function registers the pdelay_callback function with the kernel to be called every cbPeriod usec. cbPeriod should be an integer multiple of the hardclock period (500 usec). Alternatively, this initialization could have been done in pdelay_create_instance since the callback structure can only support one instance in each class at the moment.

 1  void pdelay_init_class (void)
 2  {
 3    int poll_freq = cbPeriod;		// callback period
 4    int ticks = PLUGIN_MSR_USEC2TICKS_FCT(poll_freq);
 5  
 6    pdelay_class.classid = PDELAY_ID;
 7    pdelay_class.itype   = RP_INTERFACE_TYPE_HLIST;
 8    pdelay_class.create_instance = pdelay_create_instance;
 9  
10    global_pdelay_inst_ptr = NULL;
11  
12    PLUGIN_MSR_CLOCK_HANDLER_FCT(pdelay_callback,
13  			MSR_CLOCK_HANDLER_PCU_ID, ticks);
14  }
The pdelay_init_class code tour:

The pdelay_create_instance Function

The pdelay_create_instance function is straightforward except that global_pdelay_inst_ptr (Line 23) must not be set until the instance variables have been initialized since a NULL value prevents the callback function from taking affect.

 1  struct rp_instance * pdelay_create_instance (
 2  	struct rp_class *myclass,
 3  	u_int32_t instanceid
 4  ) {
 5    struct pdelay_instance *myinst; 
 6    int			s;
 7  
 8    MSR_PLUGIN_MALLOC(myinst, struct pdelay_instance *,
 9    			sizeof(struct pdelay_instance), M_MSR, M_WAITOK);
10    if (myinst == NULL)	return NULL;
11  
12    ... standard instance initialization ...
13  
14    msr_initBuffer(&myinst->qhead);	// Instance specific processing
15    myinst->qlen       = 0;
16    myinst->pkt_count  = 0;
17    myinst->drop_count = 0;
18    myinst->fwd_count  = 0;
19    myinst->max_qlen   = 0;
20    myinst->delay_time = DELAY_TIME;
21    myinst->earliest_depart_time = 0;
22  
23    global_pdelay_inst_ptr = (struct rp_instance *) myinst;
24  
25    return (struct rp_instance *)myinst;
26  }
  

Tutorial >> More Plugins TOC