diff options
Diffstat (limited to 'net/ipv4/udp.c')
| -rw-r--r-- | net/ipv4/udp.c | 132 | 
1 files changed, 102 insertions, 30 deletions
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 0a073a26372..3159d16441d 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -902,9 +902,9 @@ int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  	ipc.addr = inet->inet_saddr;  	ipc.oif = sk->sk_bound_dev_if; -	err = sock_tx_timestamp(sk, &ipc.tx_flags); -	if (err) -		return err; + +	sock_tx_timestamp(sk, &ipc.tx_flags); +  	if (msg->msg_controllen) {  		err = ip_cmsg_send(sock_net(sk), msg, &ipc);  		if (err) @@ -1131,6 +1131,8 @@ static unsigned int first_packet_length(struct sock *sk)  	spin_lock_bh(&rcvq->lock);  	while ((skb = skb_peek(rcvq)) != NULL &&  		udp_lib_checksum_complete(skb)) { +		UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_CSUMERRORS, +				 IS_UDPLITE(sk));  		UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS,  				 IS_UDPLITE(sk));  		atomic_inc(&sk->sk_drops); @@ -1286,8 +1288,10 @@ out:  csum_copy_err:  	slow = lock_sock_fast(sk); -	if (!skb_kill_datagram(sk, skb, flags)) +	if (!skb_kill_datagram(sk, skb, flags)) { +		UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite);  		UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite); +	}  	unlock_sock_fast(sk, slow);  	if (noblock) @@ -1513,7 +1517,7 @@ int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)  	if (rcu_access_pointer(sk->sk_filter) &&  	    udp_lib_checksum_complete(skb)) -		goto drop; +		goto csum_error;  	if (sk_rcvqueues_full(sk, skb, sk->sk_rcvbuf)) @@ -1533,6 +1537,8 @@ int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)  	return rc; +csum_error: +	UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite);  drop:  	UDP_INC_STATS_BH(sock_net(sk), UDP_MIB_INERRORS, is_udplite);  	atomic_inc(&sk->sk_drops); @@ -1749,6 +1755,7 @@ csum_error:  		       proto == IPPROTO_UDPLITE ? "Lite" : "",  		       &saddr, ntohs(uh->source), &daddr, ntohs(uh->dest),  		       ulen); +	UDP_INC_STATS_BH(net, UDP_MIB_CSUMERRORS, proto == IPPROTO_UDPLITE);  drop:  	UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto == IPPROTO_UDPLITE);  	kfree_skb(skb); @@ -2279,31 +2286,88 @@ void __init udp_init(void)  int udp4_ufo_send_check(struct sk_buff *skb)  { -	const struct iphdr *iph; -	struct udphdr *uh; - -	if (!pskb_may_pull(skb, sizeof(*uh))) +	if (!pskb_may_pull(skb, sizeof(struct udphdr)))  		return -EINVAL; -	iph = ip_hdr(skb); -	uh = udp_hdr(skb); +	if (likely(!skb->encapsulation)) { +		const struct iphdr *iph; +		struct udphdr *uh; + +		iph = ip_hdr(skb); +		uh = udp_hdr(skb); -	uh->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len, -				       IPPROTO_UDP, 0); -	skb->csum_start = skb_transport_header(skb) - skb->head; -	skb->csum_offset = offsetof(struct udphdr, check); -	skb->ip_summed = CHECKSUM_PARTIAL; +		uh->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, skb->len, +				IPPROTO_UDP, 0); +		skb->csum_start = skb_transport_header(skb) - skb->head; +		skb->csum_offset = offsetof(struct udphdr, check); +		skb->ip_summed = CHECKSUM_PARTIAL; +	}  	return 0;  } +static struct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb, +		netdev_features_t features) +{ +	struct sk_buff *segs = ERR_PTR(-EINVAL); +	int mac_len = skb->mac_len; +	int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb); +	int outer_hlen; +	netdev_features_t enc_features; + +	if (unlikely(!pskb_may_pull(skb, tnl_hlen))) +		goto out; + +	skb->encapsulation = 0; +	__skb_pull(skb, tnl_hlen); +	skb_reset_mac_header(skb); +	skb_set_network_header(skb, skb_inner_network_offset(skb)); +	skb->mac_len = skb_inner_network_offset(skb); + +	/* segment inner packet. */ +	enc_features = skb->dev->hw_enc_features & netif_skb_features(skb); +	segs = skb_mac_gso_segment(skb, enc_features); +	if (!segs || IS_ERR(segs)) +		goto out; + +	outer_hlen = skb_tnl_header_len(skb); +	skb = segs; +	do { +		struct udphdr *uh; +		int udp_offset = outer_hlen - tnl_hlen; + +		skb->mac_len = mac_len; + +		skb_push(skb, outer_hlen); +		skb_reset_mac_header(skb); +		skb_set_network_header(skb, mac_len); +		skb_set_transport_header(skb, udp_offset); +		uh = udp_hdr(skb); +		uh->len = htons(skb->len - udp_offset); + +		/* csum segment if tunnel sets skb with csum. */ +		if (unlikely(uh->check)) { +			struct iphdr *iph = ip_hdr(skb); + +			uh->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr, +						       skb->len - udp_offset, +						       IPPROTO_UDP, 0); +			uh->check = csum_fold(skb_checksum(skb, udp_offset, +							   skb->len - udp_offset, 0)); +			if (uh->check == 0) +				uh->check = CSUM_MANGLED_0; + +		} +		skb->ip_summed = CHECKSUM_NONE; +	} while ((skb = skb->next)); +out: +	return segs; +} +  struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,  	netdev_features_t features)  {  	struct sk_buff *segs = ERR_PTR(-EINVAL);  	unsigned int mss; -	int offset; -	__wsum csum; -  	mss = skb_shinfo(skb)->gso_size;  	if (unlikely(skb->len <= mss))  		goto out; @@ -2313,6 +2377,7 @@ struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,  		int type = skb_shinfo(skb)->gso_type;  		if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY | +				      SKB_GSO_UDP_TUNNEL |  				      SKB_GSO_GRE) ||  			     !(type & (SKB_GSO_UDP))))  			goto out; @@ -2323,20 +2388,27 @@ struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb,  		goto out;  	} -	/* Do software UFO. Complete and fill in the UDP checksum as HW cannot -	 * do checksum of UDP packets sent as multiple IP fragments. -	 */ -	offset = skb_checksum_start_offset(skb); -	csum = skb_checksum(skb, offset, skb->len - offset, 0); -	offset += skb->csum_offset; -	*(__sum16 *)(skb->data + offset) = csum_fold(csum); -	skb->ip_summed = CHECKSUM_NONE; -  	/* Fragment the skb. IP headers of the fragments are updated in  	 * inet_gso_segment()  	 */ -	segs = skb_segment(skb, features); +	if (skb->encapsulation && skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL) +		segs = skb_udp_tunnel_segment(skb, features); +	else { +		int offset; +		__wsum csum; + +		/* Do software UFO. Complete and fill in the UDP checksum as +		 * HW cannot do checksum of UDP packets sent as multiple +		 * IP fragments. +		 */ +		offset = skb_checksum_start_offset(skb); +		csum = skb_checksum(skb, offset, skb->len - offset, 0); +		offset += skb->csum_offset; +		*(__sum16 *)(skb->data + offset) = csum_fold(csum); +		skb->ip_summed = CHECKSUM_NONE; + +		segs = skb_segment(skb, features); +	}  out:  	return segs;  } -  |