diff options
Diffstat (limited to 'net/core')
-rw-r--r-- | net/core/skbuff.c | 73 |
1 files changed, 59 insertions, 14 deletions
diff --git a/net/core/skbuff.c b/net/core/skbuff.c index b1f628741f4c..18e224af05a6 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c | |||
@@ -2428,6 +2428,7 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2428 | { | 2428 | { |
2429 | struct sk_buff *segs = NULL; | 2429 | struct sk_buff *segs = NULL; |
2430 | struct sk_buff *tail = NULL; | 2430 | struct sk_buff *tail = NULL; |
2431 | struct sk_buff *fskb = skb_shinfo(skb)->frag_list; | ||
2431 | unsigned int mss = skb_shinfo(skb)->gso_size; | 2432 | unsigned int mss = skb_shinfo(skb)->gso_size; |
2432 | unsigned int doffset = skb->data - skb_mac_header(skb); | 2433 | unsigned int doffset = skb->data - skb_mac_header(skb); |
2433 | unsigned int offset = doffset; | 2434 | unsigned int offset = doffset; |
@@ -2447,7 +2448,6 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2447 | struct sk_buff *nskb; | 2448 | struct sk_buff *nskb; |
2448 | skb_frag_t *frag; | 2449 | skb_frag_t *frag; |
2449 | int hsize; | 2450 | int hsize; |
2450 | int k; | ||
2451 | int size; | 2451 | int size; |
2452 | 2452 | ||
2453 | len = skb->len - offset; | 2453 | len = skb->len - offset; |
@@ -2460,9 +2460,36 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2460 | if (hsize > len || !sg) | 2460 | if (hsize > len || !sg) |
2461 | hsize = len; | 2461 | hsize = len; |
2462 | 2462 | ||
2463 | nskb = alloc_skb(hsize + doffset + headroom, GFP_ATOMIC); | 2463 | if (!hsize && i >= nfrags) { |
2464 | if (unlikely(!nskb)) | 2464 | BUG_ON(fskb->len != len); |
2465 | goto err; | 2465 | |
2466 | pos += len; | ||
2467 | nskb = skb_clone(fskb, GFP_ATOMIC); | ||
2468 | fskb = fskb->next; | ||
2469 | |||
2470 | if (unlikely(!nskb)) | ||
2471 | goto err; | ||
2472 | |||
2473 | hsize = skb_end_pointer(nskb) - nskb->head; | ||
2474 | if (skb_cow_head(nskb, doffset + headroom)) { | ||
2475 | kfree_skb(nskb); | ||
2476 | goto err; | ||
2477 | } | ||
2478 | |||
2479 | nskb->truesize += skb_end_pointer(nskb) - nskb->head - | ||
2480 | hsize; | ||
2481 | skb_release_head_state(nskb); | ||
2482 | __skb_push(nskb, doffset); | ||
2483 | } else { | ||
2484 | nskb = alloc_skb(hsize + doffset + headroom, | ||
2485 | GFP_ATOMIC); | ||
2486 | |||
2487 | if (unlikely(!nskb)) | ||
2488 | goto err; | ||
2489 | |||
2490 | skb_reserve(nskb, headroom); | ||
2491 | __skb_put(nskb, doffset); | ||
2492 | } | ||
2466 | 2493 | ||
2467 | if (segs) | 2494 | if (segs) |
2468 | tail->next = nskb; | 2495 | tail->next = nskb; |
@@ -2473,13 +2500,15 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2473 | __copy_skb_header(nskb, skb); | 2500 | __copy_skb_header(nskb, skb); |
2474 | nskb->mac_len = skb->mac_len; | 2501 | nskb->mac_len = skb->mac_len; |
2475 | 2502 | ||
2476 | skb_reserve(nskb, headroom); | ||
2477 | skb_reset_mac_header(nskb); | 2503 | skb_reset_mac_header(nskb); |
2478 | skb_set_network_header(nskb, skb->mac_len); | 2504 | skb_set_network_header(nskb, skb->mac_len); |
2479 | nskb->transport_header = (nskb->network_header + | 2505 | nskb->transport_header = (nskb->network_header + |
2480 | skb_network_header_len(skb)); | 2506 | skb_network_header_len(skb)); |
2481 | skb_copy_from_linear_data(skb, skb_put(nskb, doffset), | 2507 | skb_copy_from_linear_data(skb, nskb->data, doffset); |
2482 | doffset); | 2508 | |
2509 | if (pos >= offset + len) | ||
2510 | continue; | ||
2511 | |||
2483 | if (!sg) { | 2512 | if (!sg) { |
2484 | nskb->ip_summed = CHECKSUM_NONE; | 2513 | nskb->ip_summed = CHECKSUM_NONE; |
2485 | nskb->csum = skb_copy_and_csum_bits(skb, offset, | 2514 | nskb->csum = skb_copy_and_csum_bits(skb, offset, |
@@ -2489,14 +2518,11 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2489 | } | 2518 | } |
2490 | 2519 | ||
2491 | frag = skb_shinfo(nskb)->frags; | 2520 | frag = skb_shinfo(nskb)->frags; |
2492 | k = 0; | ||
2493 | 2521 | ||
2494 | skb_copy_from_linear_data_offset(skb, offset, | 2522 | skb_copy_from_linear_data_offset(skb, offset, |
2495 | skb_put(nskb, hsize), hsize); | 2523 | skb_put(nskb, hsize), hsize); |
2496 | 2524 | ||
2497 | while (pos < offset + len) { | 2525 | while (pos < offset + len && i < nfrags) { |
2498 | BUG_ON(i >= nfrags); | ||
2499 | |||
2500 | *frag = skb_shinfo(skb)->frags[i]; | 2526 | *frag = skb_shinfo(skb)->frags[i]; |
2501 | get_page(frag->page); | 2527 | get_page(frag->page); |
2502 | size = frag->size; | 2528 | size = frag->size; |
@@ -2506,20 +2532,39 @@ struct sk_buff *skb_segment(struct sk_buff *skb, int features) | |||
2506 | frag->size -= offset - pos; | 2532 | frag->size -= offset - pos; |
2507 | } | 2533 | } |
2508 | 2534 | ||
2509 | k++; | 2535 | skb_shinfo(nskb)->nr_frags++; |
2510 | 2536 | ||
2511 | if (pos + size <= offset + len) { | 2537 | if (pos + size <= offset + len) { |
2512 | i++; | 2538 | i++; |
2513 | pos += size; | 2539 | pos += size; |
2514 | } else { | 2540 | } else { |
2515 | frag->size -= pos + size - (offset + len); | 2541 | frag->size -= pos + size - (offset + len); |
2516 | break; | 2542 | goto skip_fraglist; |
2517 | } | 2543 | } |
2518 | 2544 | ||
2519 | frag++; | 2545 | frag++; |
2520 | } | 2546 | } |
2521 | 2547 | ||
2522 | skb_shinfo(nskb)->nr_frags = k; | 2548 | if (pos < offset + len) { |
2549 | struct sk_buff *fskb2 = fskb; | ||
2550 | |||
2551 | BUG_ON(pos + fskb->len != offset + len); | ||
2552 | |||
2553 | pos += fskb->len; | ||
2554 | fskb = fskb->next; | ||
2555 | |||
2556 | if (fskb2->next) { | ||
2557 | fskb2 = skb_clone(fskb2, GFP_ATOMIC); | ||
2558 | if (!fskb2) | ||
2559 | goto err; | ||
2560 | } else | ||
2561 | skb_get(fskb2); | ||
2562 | |||
2563 | BUG_ON(skb_shinfo(nskb)->frag_list); | ||
2564 | skb_shinfo(nskb)->frag_list = fskb2; | ||
2565 | } | ||
2566 | |||
2567 | skip_fraglist: | ||
2523 | nskb->data_len = len - hsize; | 2568 | nskb->data_len = len - hsize; |
2524 | nskb->len += nskb->data_len; | 2569 | nskb->len += nskb->data_len; |
2525 | nskb->truesize += nskb->data_len; | 2570 | nskb->truesize += nskb->data_len; |