Re: [PATCH v4 net-next] net: introduce dev_set_forwarding()

Eric Dumazet
2013-11-07T21:54:52Z

On Fri, 2013-11-08 at 05:31 +0800, Herbert Xu wrote:

> Sorry David, I just realised that this patch doesn't address
> this problem fully. While we can stop the generation of these
> packets in our own stack, if they're coming from the virt host
> or another guest, there is nothing we can do to stop them.
>
> So given virtio_net is now generating such packets, our choices
> are either to linearise them or deal with them properly in skb_segment.

Hi Herbert

I believe I did this on my patch.

Note that there is absolutely no requirement on how are the skb found in
frag_list (their length is not a multiple of MSS)

For the ease of discussion, once patched skb_segment() looks like :

/**
* skb_segment - Perform protocol segmentation on skb.
* @skb: buffer to segment
* @features: features for the output path (see dev->features)
*
* This function performs segmentation on the given skb. It returns
* a pointer to the first in a list of new skbs for the segments.
* In case of error it returns ERR_PTR(err).
*/
struct sk_buff *skb_segment(struct sk_buff *head_skb, netdev_features_t features)
{
struct sk_buff *segs = NULL;
struct sk_buff *tail = NULL;
struct sk_buff *cskb = head_skb;
unsigned int mss = skb_shinfo(head_skb)->gso_size;
unsigned int doffset = head_skb->data - skb_mac_header(head_skb);
unsigned int tot_len; /* should reach head_skb->len at the end */
unsigned int offset = doffset; /* offset in cskb->data */
unsigned int tnl_hlen = skb_tnl_header_len(head_skb);
unsigned int headroom;
unsigned int len;
__be16 proto;
bool csum;
int sg = !!(features & NETIF_F_SG);
int cur_frag = 0, nfrags = skb_shinfo(cskb)->nr_frags;
unsigned int data_len, cur_frag_offset = 0;
int err = -ENOMEM;

proto = skb_network_protocol(head_skb);
if (unlikely(!proto))
return ERR_PTR(-EINVAL);

csum = !!can_checksum_protocol(features, proto);
__skb_push(head_skb, doffset);
headroom = skb_headroom(head_skb);

for (tot_len = doffset; tot_len < head_skb->len; tot_len += len) {
struct sk_buff *nskb;
skb_frag_t *frag;
int hsize, size, remain;

len = head_skb->len - tot_len;
if (len > mss)
len = mss;

hsize = skb_headlen(cskb) - offset;
if (hsize < 0)
hsize = 0;
if (hsize > len || !sg)
hsize = len;

nskb = __alloc_skb(hsize + doffset + headroom,
GFP_ATOMIC, skb_alloc_rx_flag(head_skb),
NUMA_NO_NODE);

if (unlikely(!nskb))
goto err;

skb_reserve(nskb, headroom);
__skb_put(nskb, doffset);

if (segs)
tail->next = nskb;
else
segs = nskb;
tail = nskb;

__copy_skb_header(nskb, head_skb);
nskb->mac_len = head_skb->mac_len;

skb_headers_offset_update(nskb, skb_headroom(nskb) - headroom);

skb_copy_from_linear_data_offset(head_skb, -tnl_hlen,
nskb->data - tnl_hlen,
doffset + tnl_hlen);

if (!sg) {
nskb->ip_summed = CHECKSUM_NONE;
nskb->csum = skb_copy_and_csum_bits(head_skb, tot_len,
skb_put(nskb, len),
len, 0);
offset += len;
continue;
}

frag = skb_shinfo(nskb)->frags;

skb_copy_from_linear_data_offset(cskb, offset,
skb_put(nskb, hsize), hsize);
offset += hsize;

nskb->data_len = len - hsize;
nskb->len += nskb->data_len;
nskb->truesize += nskb->data_len;

skb_shinfo(nskb)->tx_flags = skb_shinfo(head_skb)->tx_flags & SKBTX_SHARED_FRAG;

for (data_len = 0; data_len < nskb->data_len; data_len += remain) {
remain = nskb->data_len - data_len;
if (unlikely(cur_frag >= nfrags)) {
if (cskb == head_skb)
cskb = skb_shinfo(head_skb)->frag_list;
else
cskb = cskb->next;
if (!cskb) {
WARN_ON_ONCE(1);
goto err;
}
cur_frag = 0;
cur_frag_offset = 0;
nfrags = skb_shinfo(cskb)->nr_frags;
offset = 0;
if (skb_headlen(cskb)) {
char *data;
struct page *page;

remain = min_t(int, remain, skb_headlen(cskb));
pr_err_once("remain %d\n", remain);
if (likely(cskb->head_frag)) {
data = cskb->data;
page = virt_to_head_page(data);
get_page(page);
} else {
data = __netdev_alloc_frag(SKB_DATA_ALIGN(remain),
GFP_ATOMIC);
/* Really this should not happen, fix the caller ! */
WARN_ON_ONCE(1);
if (!data)
goto err;
memcpy(data, cskb->data, remain);
page = virt_to_head_page(data);
}
frag->page.p = page;
frag->page_offset = data - (char *)page_address(page);
skb_frag_size_set(frag, remain);
frag++;
offset = remain;
continue;
}
}
*frag = skb_shinfo(cskb)->frags[cur_frag];
__skb_frag_ref(frag);

frag->page_offset += cur_frag_offset;
skb_frag_size_sub(frag, cur_frag_offset);
size = skb_frag_size(frag);

if (size <= remain) {
cur_frag++;
cur_frag_offset = 0;
remain = size;
} else {
skb_frag_size_set(frag, remain);
cur_frag_offset += remain;
}

frag++;
}

skb_shinfo(nskb)->nr_frags = frag - skb_shinfo(nskb)->frags;

if (!csum) {
nskb->csum = skb_checksum(nskb, doffset,
nskb->len - doffset, 0);
nskb->ip_summed = CHECKSUM_NONE;
}
}

return segs;

err:
while ((cskb = segs)) {
segs = cskb->next;
kfree_skb(cskb);
}
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(skb_segment);