Example #1
0
struct sk_buff *tcp_gso_segment(struct sk_buff *skb,
                                netdev_features_t features)
{
    struct sk_buff *segs = ERR_PTR(-EINVAL);
    unsigned int sum_truesize = 0;
    struct tcphdr *th;
    unsigned int thlen;
    unsigned int seq;
    __be32 delta;
    unsigned int oldlen;
    unsigned int mss;
    struct sk_buff *gso_skb = skb;
    __sum16 newcheck;
    bool ooo_okay, copy_destructor;

    th = tcp_hdr(skb);
    thlen = th->doff * 4;
    if (thlen < sizeof(*th))
        goto out;

    if (!pskb_may_pull(skb, thlen))
        goto out;

    oldlen = (u16)~skb->len;
    __skb_pull(skb, thlen);

    mss = skb_shinfo(skb)->gso_size;
    if (unlikely(skb->len <= mss))
        goto out;

    if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
        /* Packet is from an untrusted source, reset gso_segs. */

        skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);

        segs = NULL;
        goto out;
    }

    copy_destructor = gso_skb->destructor == tcp_wfree;
    ooo_okay = gso_skb->ooo_okay;
    /* All segments but the first should have ooo_okay cleared */
    skb->ooo_okay = 0;

    segs = skb_segment(skb, features);
    if (IS_ERR(segs))
        goto out;

    /* Only first segment might have ooo_okay set */
    segs->ooo_okay = ooo_okay;

    /* GSO partial and frag_list segmentation only requires splitting
     * the frame into an MSS multiple and possibly a remainder, both
     * cases return a GSO skb. So update the mss now.
     */
    if (skb_is_gso(segs))
        mss *= skb_shinfo(segs)->gso_segs;

    delta = htonl(oldlen + (thlen + mss));

    skb = segs;
    th = tcp_hdr(skb);
    seq = ntohl(th->seq);

    if (unlikely(skb_shinfo(gso_skb)->tx_flags & SKBTX_SW_TSTAMP))
        tcp_gso_tstamp(segs, skb_shinfo(gso_skb)->tskey, seq, mss);

    newcheck = ~csum_fold((__force __wsum)((__force u32)th->check +
                                           (__force u32)delta));

    while (skb->next) {
        th->fin = th->psh = 0;
        th->check = newcheck;

        if (skb->ip_summed == CHECKSUM_PARTIAL)
            gso_reset_checksum(skb, ~th->check);
        else
            th->check = gso_make_checksum(skb, ~th->check);

        seq += mss;
        if (copy_destructor) {
            skb->destructor = gso_skb->destructor;
            skb->sk = gso_skb->sk;
            sum_truesize += skb->truesize;
        }
        skb = skb->next;
        th = tcp_hdr(skb);

        th->seq = htonl(seq);
        th->cwr = 0;
    }

    /* Following permits TCP Small Queues to work well with GSO :
     * The callback to TCP stack will be called at the time last frag
     * is freed at TX completion, and not right now when gso_skb
     * is freed by GSO engine
     */
    if (copy_destructor) {
        swap(gso_skb->sk, skb->sk);
        swap(gso_skb->destructor, skb->destructor);
        sum_truesize += skb->truesize;
        atomic_add(sum_truesize - gso_skb->truesize,
                   &skb->sk->sk_wmem_alloc);
    }

    delta = htonl(oldlen + (skb_tail_pointer(skb) -
                            skb_transport_header(skb)) +
                  skb->data_len);
    th->check = ~csum_fold((__force __wsum)((__force u32)th->check +
                                            (__force u32)delta));
    if (skb->ip_summed == CHECKSUM_PARTIAL)
        gso_reset_checksum(skb, ~th->check);
    else
        th->check = gso_make_checksum(skb, ~th->check);
out:
    return segs;
}
Example #2
0
struct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb,
				  netdev_features_t features)
{
	struct sock *sk = gso_skb->sk;
	unsigned int sum_truesize = 0;
	struct sk_buff *segs, *seg;
	struct udphdr *uh;
	unsigned int mss;
	bool copy_dtor;
	__sum16 check;
	__be16 newlen;

	mss = skb_shinfo(gso_skb)->gso_size;
	if (gso_skb->len <= sizeof(*uh) + mss)
		return ERR_PTR(-EINVAL);

	skb_pull(gso_skb, sizeof(*uh));

	/* clear destructor to avoid skb_segment assigning it to tail */
	copy_dtor = gso_skb->destructor == sock_wfree;
	if (copy_dtor)
		gso_skb->destructor = NULL;

	segs = skb_segment(gso_skb, features);
	if (unlikely(IS_ERR_OR_NULL(segs))) {
		if (copy_dtor)
			gso_skb->destructor = sock_wfree;
		return segs;
	}

	/* GSO partial and frag_list segmentation only requires splitting
	 * the frame into an MSS multiple and possibly a remainder, both
	 * cases return a GSO skb. So update the mss now.
	 */
	if (skb_is_gso(segs))
		mss *= skb_shinfo(segs)->gso_segs;

	seg = segs;
	uh = udp_hdr(seg);

	/* compute checksum adjustment based on old length versus new */
	newlen = htons(sizeof(*uh) + mss);
	check = csum16_add(csum16_sub(uh->check, uh->len), newlen);

	for (;;) {
		if (copy_dtor) {
			seg->destructor = sock_wfree;
			seg->sk = sk;
			sum_truesize += seg->truesize;
		}

		if (!seg->next)
			break;

		uh->len = newlen;
		uh->check = check;

		if (seg->ip_summed == CHECKSUM_PARTIAL)
			gso_reset_checksum(seg, ~check);
		else
			uh->check = gso_make_checksum(seg, ~check) ? :
				    CSUM_MANGLED_0;

		seg = seg->next;
		uh = udp_hdr(seg);
	}

	/* last packet can be partial gso_size, account for that in checksum */
	newlen = htons(skb_tail_pointer(seg) - skb_transport_header(seg) +
		       seg->data_len);
	check = csum16_add(csum16_sub(uh->check, uh->len), newlen);

	uh->len = newlen;
	uh->check = check;

	if (seg->ip_summed == CHECKSUM_PARTIAL)
		gso_reset_checksum(seg, ~check);
	else
		uh->check = gso_make_checksum(seg, ~check) ? : CSUM_MANGLED_0;

	/* update refcount for the packet */
	if (copy_dtor) {
		int delta = sum_truesize - gso_skb->truesize;

		/* In some pathological cases, delta can be negative.
		 * We need to either use refcount_add() or refcount_sub_and_test()
		 */
		if (likely(delta >= 0))
			refcount_add(delta, &sk->sk_wmem_alloc);
		else
			WARN_ON_ONCE(refcount_sub_and_test(-delta, &sk->sk_wmem_alloc));
	}
	return segs;
}