162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <net/ip.h>
362306a36Sopenharmony_ci#include <net/udp.h>
462306a36Sopenharmony_ci#include <net/udplite.h>
562306a36Sopenharmony_ci#include <asm/checksum.h>
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#ifndef _HAVE_ARCH_IPV6_CSUM
862306a36Sopenharmony_ci__sum16 csum_ipv6_magic(const struct in6_addr *saddr,
962306a36Sopenharmony_ci			const struct in6_addr *daddr,
1062306a36Sopenharmony_ci			__u32 len, __u8 proto, __wsum csum)
1162306a36Sopenharmony_ci{
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci	int carry;
1462306a36Sopenharmony_ci	__u32 ulen;
1562306a36Sopenharmony_ci	__u32 uproto;
1662306a36Sopenharmony_ci	__u32 sum = (__force u32)csum;
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci	sum += (__force u32)saddr->s6_addr32[0];
1962306a36Sopenharmony_ci	carry = (sum < (__force u32)saddr->s6_addr32[0]);
2062306a36Sopenharmony_ci	sum += carry;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	sum += (__force u32)saddr->s6_addr32[1];
2362306a36Sopenharmony_ci	carry = (sum < (__force u32)saddr->s6_addr32[1]);
2462306a36Sopenharmony_ci	sum += carry;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	sum += (__force u32)saddr->s6_addr32[2];
2762306a36Sopenharmony_ci	carry = (sum < (__force u32)saddr->s6_addr32[2]);
2862306a36Sopenharmony_ci	sum += carry;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	sum += (__force u32)saddr->s6_addr32[3];
3162306a36Sopenharmony_ci	carry = (sum < (__force u32)saddr->s6_addr32[3]);
3262306a36Sopenharmony_ci	sum += carry;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	sum += (__force u32)daddr->s6_addr32[0];
3562306a36Sopenharmony_ci	carry = (sum < (__force u32)daddr->s6_addr32[0]);
3662306a36Sopenharmony_ci	sum += carry;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	sum += (__force u32)daddr->s6_addr32[1];
3962306a36Sopenharmony_ci	carry = (sum < (__force u32)daddr->s6_addr32[1]);
4062306a36Sopenharmony_ci	sum += carry;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	sum += (__force u32)daddr->s6_addr32[2];
4362306a36Sopenharmony_ci	carry = (sum < (__force u32)daddr->s6_addr32[2]);
4462306a36Sopenharmony_ci	sum += carry;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	sum += (__force u32)daddr->s6_addr32[3];
4762306a36Sopenharmony_ci	carry = (sum < (__force u32)daddr->s6_addr32[3]);
4862306a36Sopenharmony_ci	sum += carry;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	ulen = (__force u32)htonl((__u32) len);
5162306a36Sopenharmony_ci	sum += ulen;
5262306a36Sopenharmony_ci	carry = (sum < ulen);
5362306a36Sopenharmony_ci	sum += carry;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	uproto = (__force u32)htonl(proto);
5662306a36Sopenharmony_ci	sum += uproto;
5762306a36Sopenharmony_ci	carry = (sum < uproto);
5862306a36Sopenharmony_ci	sum += carry;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	return csum_fold((__force __wsum)sum);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ciEXPORT_SYMBOL(csum_ipv6_magic);
6362306a36Sopenharmony_ci#endif
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ciint udp6_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	int err;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	UDP_SKB_CB(skb)->partial_cov = 0;
7062306a36Sopenharmony_ci	UDP_SKB_CB(skb)->cscov = skb->len;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	if (proto == IPPROTO_UDPLITE) {
7362306a36Sopenharmony_ci		err = udplite_checksum_init(skb, uh);
7462306a36Sopenharmony_ci		if (err)
7562306a36Sopenharmony_ci			return err;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		if (UDP_SKB_CB(skb)->partial_cov) {
7862306a36Sopenharmony_ci			skb->csum = ip6_compute_pseudo(skb, proto);
7962306a36Sopenharmony_ci			return 0;
8062306a36Sopenharmony_ci		}
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* To support RFC 6936 (allow zero checksum in UDP/IPV6 for tunnels)
8462306a36Sopenharmony_ci	 * we accept a checksum of zero here. When we find the socket
8562306a36Sopenharmony_ci	 * for the UDP packet we'll check if that socket allows zero checksum
8662306a36Sopenharmony_ci	 * for IPv6 (set by socket option).
8762306a36Sopenharmony_ci	 *
8862306a36Sopenharmony_ci	 * Note, we are only interested in != 0 or == 0, thus the
8962306a36Sopenharmony_ci	 * force to int.
9062306a36Sopenharmony_ci	 */
9162306a36Sopenharmony_ci	err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
9262306a36Sopenharmony_ci							ip6_compute_pseudo);
9362306a36Sopenharmony_ci	if (err)
9462306a36Sopenharmony_ci		return err;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
9762306a36Sopenharmony_ci		/* If SW calculated the value, we know it's bad */
9862306a36Sopenharmony_ci		if (skb->csum_complete_sw)
9962306a36Sopenharmony_ci			return 1;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		/* HW says the value is bad. Let's validate that.
10262306a36Sopenharmony_ci		 * skb->csum is no longer the full packet checksum,
10362306a36Sopenharmony_ci		 * so don't treat is as such.
10462306a36Sopenharmony_ci		 */
10562306a36Sopenharmony_ci		skb_checksum_complete_unset(skb);
10662306a36Sopenharmony_ci	}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return 0;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ciEXPORT_SYMBOL(udp6_csum_init);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci/* Function to set UDP checksum for an IPv6 UDP packet. This is intended
11362306a36Sopenharmony_ci * for the simple case like when setting the checksum for a UDP tunnel.
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_civoid udp6_set_csum(bool nocheck, struct sk_buff *skb,
11662306a36Sopenharmony_ci		   const struct in6_addr *saddr,
11762306a36Sopenharmony_ci		   const struct in6_addr *daddr, int len)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct udphdr *uh = udp_hdr(skb);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	if (nocheck)
12262306a36Sopenharmony_ci		uh->check = 0;
12362306a36Sopenharmony_ci	else if (skb_is_gso(skb))
12462306a36Sopenharmony_ci		uh->check = ~udp_v6_check(len, saddr, daddr, 0);
12562306a36Sopenharmony_ci	else if (skb->ip_summed == CHECKSUM_PARTIAL) {
12662306a36Sopenharmony_ci		uh->check = 0;
12762306a36Sopenharmony_ci		uh->check = udp_v6_check(len, saddr, daddr, lco_csum(skb));
12862306a36Sopenharmony_ci		if (uh->check == 0)
12962306a36Sopenharmony_ci			uh->check = CSUM_MANGLED_0;
13062306a36Sopenharmony_ci	} else {
13162306a36Sopenharmony_ci		skb->ip_summed = CHECKSUM_PARTIAL;
13262306a36Sopenharmony_ci		skb->csum_start = skb_transport_header(skb) - skb->head;
13362306a36Sopenharmony_ci		skb->csum_offset = offsetof(struct udphdr, check);
13462306a36Sopenharmony_ci		uh->check = ~udp_v6_check(len, saddr, daddr, 0);
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ciEXPORT_SYMBOL(udp6_set_csum);
138