jbuf API Usage in the Daemon Version

The code for the daemon version exercises most of the essential jbuf APIs and is a good place to learn about their usage.

(For a general introduction to jbufs, see Using the Services SDK jbuf Library for Packet Processing.)

Some areas to note are as highlighted here.

The Main Packet Loop

The pkt_buf variable points to a the packet, which is a chain of jbuf structures.

struct jbuf * pkt_buf

The main packet loop in reassembler_process_packet() calls the msp_data_recv() function to dequeue packets from the FIFO; calls the pullup_bytes() utility to pull contiguous bytes of data into the first jbuf in the chain to facilitate analysis; retrieves the IP header and either calls process_fragment() to handle a fragment or send_packet() to send the packet out. jbuf APIs used here are jbuf_free() to free memory used for the packet buffer and jbuf_to_d() to convert the jbuf pointer to a data pointer of the required type.

    while(!do_shutdown) {

        // Dequeue a packet from the rx-fifo
        pkt_buf = msp_data_recv(params->dhandle, &type);

        if(pkt_buf == NULL) { // Didn't get anything
            continue;
        }

        if(type != MSP_MSG_TYPE_PACKET) { // Didn't get network traffic
            DLOG(LOG_WARNING, "%s: Message wasn't a packet...dropping",
                __func__);
            jbuf_free(pkt_buf);
            continue;
        }

        if(pullup_bytes(&pkt_buf, sizeof(struct ip))) {

            DLOG(LOG_ERR, "%s: Dropped a packet because there's not enough "
                "bytes to form an IP header and a pullup failed.", __func__);

            jbuf_free(pkt_buf);
            continue;
        }

        // Get IP header
        ip_pkt = jbuf_to_d(pkt_buf, struct ip *);

        if(ip_pkt->ip_off & htons(IP_MF | IP_OFFMASK)) { // It's a fragment

            process_fragment(pkt_buf, cpu, &params->dhandle);

        } else {

            send_packet(pkt_buf, &params->dhandle);
        }
    }

Making Packet Bytes Contiguous

The pullup_bytes() utility is used in both versions of the example to rearrange the jbuf chain so bytes are contiguous and allow the conversion to data with the jbuf_to_d() call. The function calls the jbuf_pullup() function to do this.

static status_t
pullup_bytes(struct jbuf ** pkt_buf, uint16_t num_bytes)
{
    struct jbuf * tmp_buf;

    if((*pkt_buf)->jb_len < num_bytes) {
        tmp_buf = jbuf_pullup((*pkt_buf), num_bytes);

        if(!tmp_buf) { // check it didn't fail
            return EFAIL;
        }

        *pkt_buf = tmp_buf;
    }
    return SUCCESS;
}

Processing a Fragment

The process_fragment() function is where most of the code that is specific to the daemon version is found. When you use a plugin, the framework does all this work for you.

The algorithm receives fragments until a "hole" is filled. The code creates an ordered list of fragments, each with a potential leading and trailing hole. The first fragment is known to have no leading hole and the last is known to have no trailing hole. An entry looks as follows:

typedef struct fragment_entry_s {
    fragment_hole_t   leading_hole;     
    struct jbuf *     jb;            
    fragment_hole_t   trailing_hole; 
    
    bool is_leading_hole;  
    bool is_trailing_hole; 
    
    // for list at this hash bucket:
    TAILQ_ENTRY(fragment_entry_s) entries; // next and prev list entries
} fragment_entry_t;

A hole is defined as follows:

typedef struct fragment_hole_s {
    uint16_t start;   // start of hole offset (counted in fragment blocks)
    uint16_t end;     // end of hole offset (counted in fragment blocks)
} fragment_hole_t;

When a fragment is received, the code walks the list to find its position and inserts it to fill in more of the hole, until there are no more holes and the last fragment has been received. If a fragment overlaps other fragments, the code calls jbuf APIs to trim off and discard the overlap. After trimming the overlap, if two fragments fit back to back, there is no more hole between them.

Sample jbuf Calls

The functions process_fragment() and send_fragment_list() use various jbuf API calls to manipulate the packet buffers. This section gives a few examples.

jbuf_free()

jbuf_free() frees the memory for all the jbufs in the chain under various (usually failure) conditions; for example:

if(rc == MSP_DATA_SEND_FAIL) {

        DLOG(LOG_ERR, "%s: Failed to forward packet using msp_data_send().",
            __func__);
        jbuf_free(pkt_buf);

jbuf_get()

jbuf_get() allocates a jbuf for a packet; for example:

struct jbuf * pkt_buf;
...
pkt_buf = jbuf_get();

jbuf_dup()

jbuf_dup() makes a full copy of a jbuf chain; for example:

fragment_entry_t * fe;

...

// put this jbuf in the fragment list
    fe = msp_objcache_alloc(frag_handle, cpu, obj_cache_id);
    if(fe == NULL) {
        // Release the entry lock
        INSIST_ERR(msp_spinlock_unlock(&entry->lock) == MSP_OK);
        jbuf_free(pkt_buf);
        DLOG(LOG_ERR, "%s: Failed to allocate object cache for a fragment"
                " entry.", __func__);
        return EFAIL;
    }
    
    fe->jb = jbuf_dup(pkt_buf);
    jbuf_free(pkt_buf);

jbuf_split()

jbuf_split() partitions a jbuf chain in two pieces and returns the tail, or all but the first len bytes of the jbuf; for example:

struct jbuf * tmp;
fragment_entry_t * fe;

...

 // Chop off IP header if it is not the first fragment
    if(offset != 0) {
        tmp = jbuf_split(fe->jb, hdr_len);
        if(tmp != NULL) {
            jbuf_free(fe->jb);
            fe->jb = tmp;
        } else {
            INSIST_ERR(msp_spinlock_unlock(&entry->lock) == MSP_OK);
            jbuf_free(fe->jb);
            msp_objcache_free(frag_handle, fe, cpu, obj_cache_id);
            DLOG(LOG_ERR, "%s: Error splitting jbuf (1)", __func__);
            return EFAIL;
        } 
        ...

jbuf_cat()

jbuf_cat() concatenates a jbuf chain according to the parameters back to front; for example, this appends a packet buffer to an IP header:

while(1) {
        
        len = jbuf_total_len(pkt_buf) + jbuf_total_len(fe->jb);
        if(len < mtu) {
            // safe to glue the 2 together; don't send it yet though
            jbuf_cat(pkt_buf, fe->jb);

        } else if(len == mtu) {
            
            // safe to glue the 2 together; send it now
            jbuf_cat(pkt_buf, fe->jb);
...

jbuf_copychain()

jbuf_copychain() copies data to a location from the beginning of the buffer specified by the second (offset) argument; for example:

struct ip * ip_pkt;

// ip_hl is the IP header length declared in  struct ip)

...

      if(!TAILQ_EMPTY(&entry->flist)) { // if there's more to send...
      
      // save IP header in another jbuf for next fragment to send

         ip_hdr = jbuf_copychain(pkt_buf, 0, ip_pkt->ip_hl * 
                        sizeof(uint32_t));
         if(ip_hdr) {
               DLOG(LOG_ERR, "%s: Failed to copy IP header from packet"
                           " into a new buffer (1).", __func__);
               goto failure;
          }
  ...
 

IP Reassembly in the Plugin


2007-2009 Juniper Networks, Inc. All rights reserved. The information contained herein is confidential information of Juniper Networks, Inc., and may not be used, disclosed, distributed, modified, or copied without the prior written consent of Juniper Networks, Inc. in an express license. This information is subject to change by Juniper Networks, Inc. Juniper Networks, the Juniper Networks logo, and JUNOS are registered trademarks of Juniper Networks, Inc. in the United States and other countries. All other trademarks, service marks, registered trademarks, or registered service marks are the property of their respective owners.
Generated on Sun May 30 20:26:47 2010 for Juniper Networks Partner Solution Development Platform JUNOS SDK 10.2R1 by Doxygen 1.4.5