Shaping Bandwidth by VLAN under the NetEqualizer Hood


As a followup to my recent commentary on  the history of VLAN tags, I decided to jump down into the guts of a bandwidth shaper and go over some of the techniques we use to set rate limits on a particular VLAN. When writing, I assumed the reader has a basic understanding of how data can be manipulated inside a computer program.

Let’s start with some background information. First off, the NetEqualizer bandwidth shaper is a transparent bridge.  A typical setup has two Ethernet cards —  one connected to your LAN and the other side connected to your WAN (Internet router). Before we added in our VLAN shaping, the Linux kernel bridging code would blindly transfer Ethernet packets from one side to the other, passing right through the NetEqualizer.

As these Ethernet packets pass through, they’re visible as data in the Linux kernel. Normally, they pass through unmolested — in one side out the other. However, the key to bandwidth shaping is what you do with them as they come through.

To give you a better idea of what goes inside the Linux kernel when data passes through, I’ve included a couple of snippets of C code below. This is actual Linux kernel code. I have also littered the code with some detailed explanations in line, so you don’t have to understand C to follow the logic.

Below is the C language data definition of the fields in an Ethernet header. When an Ethernet packet comes across the NetEqualizer, the contents of the Ethernet packet are put into data structures. The reason why we’re interested in the Ethernet header is that it’s where the VLAN tags are located.

Note: Code appears in italics while notes are in bold and non-italicized font.

struct vlan_ether_header {
char dst[6];     // This is six bytes for the destination MAC address.
char src[6];     // This is six bytes for the source MAC address.
short type;
short tci_vid;
short encapsulated_type;
} __attribute__ ((__packed__));

Below is the C function that finds the actual VLAN tag inside the Ethernet header in an Ethernet packet.

struct iphdr* findIph(struct sk_buff* skb, int *vlan_id) {
struct ethhdr* eh;    

// This is a pointer to a data structure of type ether net header. We first declare the pointer and will assign it later.
struct iphdr* iph = NULL;   

// This is a pointer to a data structure that contains the IP header of an IP packet (I did not show the definition of the structure).
*vlan_id = -1;                         

// Set the VLAN ID to something.

eh = (struct ethhdr*)(skb->mac_header);

/*  The SKB buffer is the standard structure for network data being passed around the kernel. It contains all the data related to IP data  including the Ethernet packet. Part of the Ethernet packet is the MAC header which is what we are interested in to find out the VLAN ID. FYI . . . SKB is the buffer that IP tables routinely use. To enforce firewall rules, they pass this buffer from rule-to-rule because everybody needs to look inside of it to decide what to do. I am not going to go into how it came into existence. Suffice to say the Ethernet packet is located in this buffer. The MAC header is a field in the SKB buffer and the above assignment copies this location to the variable eh, which is the pointer of an Ethernet header. We now have a data structure that we can access to see fields inside the Ethernet header as a packet passes through the NetEqualizer */

if (eh->h_proto == 0x0081) {
struct vlan_ether_header* veh = (struct vlan_ether_header*)(skb->mac_header);

if (veh->encapsulated_type == 0x0008) {
iph = (struct iphdr*)(skb->mac_header + sizeof(*veh));
*vlan_id = ((ntohs(veh->tci_vid)) & 0x0fff);
// BR_DEBUG_IP printk (KERN_INFO “got VLAN ID %d \n”, *vlan_id);
}
}

/* The above code snippet is where the actual VLAN ID gets put into the variable vlan_id. The FFF is a bit mask which slices the value of the VLAN ID out of the field tci_vid. It is a 12-bit number */
else {
if (eh->h_proto == 0x0008) {
iph = (struct iphdr*)(skb->mac_header + sizeof(*eh));
}
}
return iph;
}

Hopefully the code captured the spirit of the type of work that goes on in the Linux kernel to analyze packets. But, how does VLAN shaping work once you have the VLAN ID?

Well, once we have the VLAN ID of a packet, we check and see if there is a VLAN shaping rule in effect for that ID. There is a table in the Kernel with a list of all of the active VLAN shaping rules that have been specified by the user. If there is a rule for this VLAN, a counter is incrimented for the number of data bytes in the payload of the IP packet.

if (vlan_id > -1  && vlan_id < VLAN_MAX && hard_table[vlan_id + HARD_SIZE].ip == vlan_id && port_id ==2) {
                hard_table[vlan_id + HARD_SIZE].incount=hard_table[vlan_id +HARD_SIZE].incount +hsize;

The code snippet above checks to make sure the VLAN ID is valid and then it increments the byte count for that VLAN. hsize is a variable that contains the actual number of data bytes in the Ethernet packet.

The NetEqualizer keeps this counter for an entire second (it will reset it each second), and if the data coming in for the VLAN is coming in faster than the rate limit defined by a user rule for that particular VLAN ID, then the NetEqualizer will take action by actually slowing down the packet in the kernel. This in turn reduces the data rate of transfer for the VLAN.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: