Skip to content

Commit 97550f6

Browse files
wdebruijkuba-moo
authored andcommitted
net: compound page support in skb_seq_read
skb_seq_read iterates over an skb, returning pointer and length of the next data range with each call. It relies on kmap_atomic to access highmem pages when needed. An skb frag may be backed by a compound page, but kmap_atomic maps only a single page. There are not enough kmap slots to always map all pages concurrently. Instead, if kmap_atomic is needed, iterate over each page. As this increases the number of calls, avoid this unless needed. The necessary condition is captured in skb_frag_must_loop. I tried to make the change as obvious as possible. It should be easy to verify that nothing changes if skb_frag_must_loop returns false. Tested: On an x86 platform with CONFIG_HIGHMEM=y CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP=y CONFIG_NETFILTER_XT_MATCH_STRING=y Run ip link set dev lo mtu 1500 iptables -A OUTPUT -m string --string 'badstring' -algo bm -j ACCEPT dd if=/dev/urandom of=in bs=1M count=20 nc -l -p 8000 > /dev/null & nc -w 1 -q 0 localhost 8000 < in Signed-off-by: Willem de Bruijn <[email protected]> Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 29766bc commit 97550f6

File tree

2 files changed

+24
-5
lines changed

2 files changed

+24
-5
lines changed

include/linux/skbuff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,7 @@ struct skb_seq_state {
12031203
struct sk_buff *root_skb;
12041204
struct sk_buff *cur_skb;
12051205
__u8 *frag_data;
1206+
__u32 frag_off;
12061207
};
12071208

12081209
void skb_prepare_seq_read(struct sk_buff *skb, unsigned int from,

net/core/skbuff.c

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3442,6 +3442,7 @@ void skb_prepare_seq_read(struct sk_buff *skb, unsigned int from,
34423442
st->root_skb = st->cur_skb = skb;
34433443
st->frag_idx = st->stepped_offset = 0;
34443444
st->frag_data = NULL;
3445+
st->frag_off = 0;
34453446
}
34463447
EXPORT_SYMBOL(skb_prepare_seq_read);
34473448

@@ -3496,14 +3497,27 @@ unsigned int skb_seq_read(unsigned int consumed, const u8 **data,
34963497
st->stepped_offset += skb_headlen(st->cur_skb);
34973498

34983499
while (st->frag_idx < skb_shinfo(st->cur_skb)->nr_frags) {
3500+
unsigned int pg_idx, pg_off, pg_sz;
3501+
34993502
frag = &skb_shinfo(st->cur_skb)->frags[st->frag_idx];
3500-
block_limit = skb_frag_size(frag) + st->stepped_offset;
35013503

3504+
pg_idx = 0;
3505+
pg_off = skb_frag_off(frag);
3506+
pg_sz = skb_frag_size(frag);
3507+
3508+
if (skb_frag_must_loop(skb_frag_page(frag))) {
3509+
pg_idx = (pg_off + st->frag_off) >> PAGE_SHIFT;
3510+
pg_off = offset_in_page(pg_off + st->frag_off);
3511+
pg_sz = min_t(unsigned int, pg_sz - st->frag_off,
3512+
PAGE_SIZE - pg_off);
3513+
}
3514+
3515+
block_limit = pg_sz + st->stepped_offset;
35023516
if (abs_offset < block_limit) {
35033517
if (!st->frag_data)
3504-
st->frag_data = kmap_atomic(skb_frag_page(frag));
3518+
st->frag_data = kmap_atomic(skb_frag_page(frag) + pg_idx);
35053519

3506-
*data = (u8 *) st->frag_data + skb_frag_off(frag) +
3520+
*data = (u8 *)st->frag_data + pg_off +
35073521
(abs_offset - st->stepped_offset);
35083522

35093523
return block_limit - abs_offset;
@@ -3514,8 +3528,12 @@ unsigned int skb_seq_read(unsigned int consumed, const u8 **data,
35143528
st->frag_data = NULL;
35153529
}
35163530

3517-
st->frag_idx++;
3518-
st->stepped_offset += skb_frag_size(frag);
3531+
st->stepped_offset += pg_sz;
3532+
st->frag_off += pg_sz;
3533+
if (st->frag_off == skb_frag_size(frag)) {
3534+
st->frag_off = 0;
3535+
st->frag_idx++;
3536+
}
35193537
}
35203538

35213539
if (st->frag_data) {

0 commit comments

Comments
 (0)