C H A P T E R  10

image

Basic Packet Interaction and Operations

Interaction with the Packet through Unique-to-packetC Capabilities

The packet can be seen as either one of the simplest data elements in the language or one of the most complex, and hopefully both in a good way. In its simplest form, the packet is an array of bytes, as defined in cloudshield.ph, it simply looks similar to the statement below:

//==============================================================================
//  Packet Type
//
//  Each system may have a slightly different constraint on the buffer for each
//  packet.  The typedef below defines the $PACKET for the system.
//
//==============================================================================
// typedef byte $PACKET[9 * 1024 - 1];

Looking at the packet in a different way, it is comprised of multiple protocols layered one inside another. Each layer not only prescribes information about the next layer enveloped inside but also has complex definitions on the construction of the layer itself. A simple HTTP web page request over the simplest Ethernet II link has four major layers to the packet with almost 100 fields of interest. The packet is represented in packetC as an array of bytes with descriptors providing a means to break the array into headers with the individual fields to accomplish a notional network view of the packet as in Figure 10-1. To work with the packet inspecting and manipulating its construction without insight to the packet's construction can seem a bit daunting.

images

Figure 10-1. A WAN packet viewed as portions of a byte array

Fortunately, packetC introduces many different tools to work with the packet, in a form consistent with a network engineer's perspective, in order to simplify the coding of a program managing streams of packets. Not only has packetC offloaded entirely the receipt, buffering, and management of the transmission of packets, but it also provides a number of pre- and post-processing capabilities that provide an understanding of a packet before the first line of code is executed with information passed to the program through the pib and sys data structures. Descriptors introduce yet another major advancement, allowing any of the 100 fields for the packet described above to be addressed by name as if they were simple structure fields, even when one packet to the next changes the byte offset within the packet for those fields.

byte b;
b = pkt[35];

int x;
x = (int) pkt[0:3];

struct BaseType {
...
} myStruct;
myStruct = (BaseType) pkt[36:36+sizeof(BaseType)-1];


pkt [j].delete( sizeof( TcpProtocol ) );

byte barr[4] = { 6, 7, 8, 9 };
pkt[16].insert( 5, barr );

pkt.replicate();

In this chapter, some of the other packet operations specific to packetC are discussed with regard to addressing changing the macro level construction of a packet, such as its size and some interactions with how and when it gets processed.

The current packet passed into the packet module's main is accessible to the user as a byte array. There are several operations that can be done on the packet. The packet will always be referenced as pkt in packetC and is visible throughout a packet module's global, packet, and block scopes. Since the packet is treated as a byte array, full array slicing and assignment capabilities are present for the packet as either a source or destination of an operator.

Get Packet Offset

packet_offset

This unary operator takes a single descriptor field as an operand and returns an int value that indicates the offset of that field from the start of the packet. Similar to the offset operator, this operator returns the byte value of the field offset. There are a few differences. First, the offset for the field is not from the start of the descriptor but from the start of the packet. A descriptor includes an expression that is used to calculate the offset of the descriptor from the start of the packet. The result of packet_offset is the addition of this value plus the offset of the field from the start of the descriptor. Given that the values in the pib are referenced to determine the offset of a descriptor change from one packet to another, the value returned by packet_offset must be determined at run time. This is in contrast to offset, which is computed at compile time.

struct TcpProtocol {
   short   sourcePort;
   short   destPort;
   int   sequenceNum;
   int   ackNum;
   ...
};
descriptor  TcpProtocol  TcpHeader  at pib.L4_Offset;
byte  barray[8];
   ...
start = packet_offset( TcpHeader.sourcePort );
stop = packet_offset(TcpHeader.ackNum )  -  1;

// get first three fields of the protocol
barray[0:7] = pkt[ start : stop ];

The operand of packet_offset must be a field defined in a descriptor. Refer to the offset operator description section for more details on how field offsets are calculated.

Packet Operators

  • delete
  • insert
  • replicate
  • requeue

Packet Delete

The packet delete operator is used to remove bytes from a packet, often for the purpose of removing option headers, MPLS and VLAN tags, or content from the payload. To delete bytes from the current packet at a given index use PKT[X] where X = the offset of where in the packet to start deleting followed by delete (Y) where Y = H bytes to delete. The operator deletes the indicated bytes from the current packet, effectively shrinking its size and returns void (no meaningful result).

If the system is unable to delete the specified bytes from the packet, the delete operator will throw the predefined error, ERR_PKT_DELETE.

pkt[34].delete(16);
pkt[j].delete( sizeof( TcpProtocol ) );
pkt.delete(3);          // ERROR: missing subscript to specify start
pkt[0:4].delete(2);     // ERROR: starting point must be an element
pkt[25].delete;         // ERROR: must specify how many bytes to delete

Packet Insert

The current packet behaves as a dynamically-sized, zero-indexed array of bytes. Packet insertions use an index expression to specify an offset where the insertion starts and a parenthesized argument that specifies how many bytes to add at the insertion point. If an optional second argument appears within the parentheses, it specifies a byte array or array slice, whose contents are copied into the added space. The additional bytes are effectively inserted before the byte currently residing at the specified offset. As an operator, insertion returns a result of type void (no meaningful result). The packet insert operator is used to add bytes to a packet, often for the purpose of adding option headers, MPLS and VLAN tags, or content into the payload.

Given a packet offset value of n for the expansion point, a filler object to copy f, an expansion byte count b, and copy object size s:

  • If the optional, “filler object” argument is present, its size must be equal to the number of bytes being added by the expansion operation, i.e., its copied contents must completely fill up the new space. Thus, b=s and packet [n:(n+s-1)]=f.
  • If there is no data to insert (the optional argument is absent), the contents of pkt[ n : n + b-1 ] are filled with a value of 0.
  • If the specified insertion point is = last legal offset + 1, then the inserted bytes are appended to the end of the packet; otherwise it is erroneous to specify an offset greater than the offset of the current packet's last byte.

If the packet is unable to be expanded, the insert operator will throw the predefined error, ERR_PKT_INSERT.

byte barr[4] = { 6, 7, 8, 9 };
struct MyStructType {
short src;
short dest;
} myStruct = { 0xab, 0xcd };

// if pkt[16] currently = 17 and the last byte
// is at offset = 127 and has a value of 23;

// pkt[16:19] = 6, 7, 8, 17.
pkt[16].insert( 3, barr );

// Inserted bytes are 0 since fill value not specified
// pkt[16:18] = 0, 0, 17.
pkt[16].insert( 2 );

// pkt[127:129] = 23, 6, 7.
pkt[127].insert( 2, barr );

// ERROR: attempt to insert with barr shorter than insert.
// pkt[16:21] = 6, 7, 8, 9, undefined, 17.
pkt[16].insert( 5, barr );

// ERROR: attempt to insert past current legal packet end.
pkt[300].insert( 2 );

Packet Replicate

The replicate operator creates a duplicate of the packet currently being processed, as an operator it returns a result of type void (no meaningful result). The replicate operator throws ERR_PKT_NOREPLICATE if an error occurs while replicating the packet.

Regardless of the success or failure of the replicate operation, the original packet continues to be processed. A packet created through replication can be detected by inspecting a flag variable in the Packet Information Block (see section on Packet Information Block). Replicated packets must be detected and processed with care to prevent a self-perpetuating chain in which replicated packets trigger further replications. Other than the replica status flag, pib fields are carried forward into the replica with their state at the time of replication.

try {
   pkt.replicate();
      ...
}
catch( ERR_PKT_NOREPLICATE )
{…}
         catch( ... )
{…}

Packet Requeue

There are times when it is desirable to postpone processing of a given packet until some future time, e.g., a temporary lack of resources to process it. Instead of stalling the context and reducing the processing capacity of the system through a wait operation, packetC introduces the ability to place the packet back on the input queue. When the packet is requeued it is placed at the end of the list of packets awaiting assignment to a context for processing. Given that the buffer depth may ebb and flow, how soon the packet will reappear on a context for processing is not deterministic, however, information that the packet was requeued is available along with a counter of how many times. For example, the application might need to requeue the packet until a specific time and requeue rates become too high.

Requeue is not an operator so much as an action to take on the current packet at the end of processing, much like forward and drop are actions. Requeue of the packet does not occur until execution has stopped. When the packet reappears on a new context for processing, it will begin at main() as if it was new to the system. The only difference is that pib.requeueCount will be non-zero.

To select a packet to be requeued, set the pib.action field equal to REQUEUE_PACKET. This is selected as an alternative to DROP_PACKET or FORWARD_PACKET.

When current processing exits, should the packet fail to be requeued, execution will not cease and the system throws ERR_PKT_NOTREQUEUED. Should the decision to requeue the packet occur within a function and clean exit back to main() is not possible, the exit command can immediately follow the setting of pib.action, although it is highly discouraged as there may be other functions that expect to perform termination processing.

if (processingCantFinish == true)
{
  pib.action = REQUEUE_PACKET;
  exit;
};
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.107.31