SKB Segmenation in Linux Kernel

Generic Segmentation Offload (GSO), usually, comes before a device specific codes. Which means, device drivers do not have to be aware of segmentation of any kind. Basically, the segmentation may happen before calling dev_queue_xmit().

One important thing to notice is that a GSO segmentation function comes after L2 protocol, because L2 header size will remain unchanged and attached to each segment. The type of the segmentation function is determined by the Ethertype. Then, we may have GSO for MPLS and IPv4/TCP, for example.

/**
 *      __skb_gso_segment - Perform segmentation on skb.
 *      @skb: buffer to segment
 *      @features: features for the output path (see dev->features)
 *      @tx_path: whether it is called in TX path
 *
 *      This function segments the given skb and returns a list of segments
 *
 *      It may return NULL if the skb requires no segmentation.  This is
 *      only possible when GSO is used for verifying header integrity.
 */
struct sk_buff *__skb_gso_segment(struct sk_buff *skb,
                                  netdev_features_t features, bool tx_path)
{
         if (unlikely(skb_needs_check(skb, tx_path))) {
                int err;
 
                skb_warn_bad_offload(skb);
 
                if (skb_header_cloned(skb) &&
                     (err = pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
                        return ERR_PTR(err);
         }
 
         SKB_GSO_CB(skb)->mac_offset = skb_headroom(skb);
         SKB_GSO_CB(skb)->encap_level = 0;
 
         skb_reset_mac_header(skb);
         skb_reset_mac_len(skb);
 
         return skb_mac_gso_segment(skb, features);
}

skb_mac_gso_segment() will trigger a protocol specific segmentation offload functions (protocols above L2).

/**
 *      skb_mac_gso_segment - mac layer segmentation handler.
 *      @skb: buffer to segment
 *      @features: features for the output path (see dev->features)
 */
struct sk_buff *skb_mac_gso_segment(struct sk_buff *skb,
                                     netdev_features_t features)
{
         struct sk_buff *segs = ERR_PTR(-EPROTONOSUPPORT);
         struct packet_offload *ptype;
         int vlan_depth = skb->mac_len;
         __be16 type = skb_network_protocol(skb, &vlan_depth);
 
         if (unlikely(!type))
                 return ERR_PTR(-EINVAL);
 
         __skb_pull(skb, vlan_depth);
 
        rcu_read_lock();
        list_for_each_entry_rcu(ptype, &offload_base, list) {
                if (ptype->type == type && ptype->callbacks.gso_segment) {
                        if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
                                 int err;
 
                                err = ptype->callbacks.gso_send_check(skb);
                                segs = ERR_PTR(err);
                                if (err || skb_gso_ok(skb, features))
                                       break;
                                 __skb_push(skb, (skb->data -
                                                 skb_network_header(skb)));
                         }
                        segs = ptype->callbacks.gso_segment(skb, features);
                        break;
                 }
         }
         rcu_read_unlock();
 
         __skb_push(skb, skb->data - skb_mac_header(skb));
 
         return segs;
}

ptype->callbacks.gso_segment() is the callback function for a specific typeof protocol, such as MPLS. Let’s the ‘ptype->type’ matches ‘type’, where ‘type’ is equal to MPLS Unicast Ethertype (0x8847), so mpls_gso_segment() would be the callback fucntion to be called.