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