linux/net/ipv6/ip6_checksum.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include <net/ip.h>
   3#include <net/udp.h>
   4#include <net/udplite.h>
   5#include <asm/checksum.h>
   6
   7#ifndef _HAVE_ARCH_IPV6_CSUM
   8__sum16 csum_ipv6_magic(const struct in6_addr *saddr,
   9                        const struct in6_addr *daddr,
  10                        __u32 len, __u8 proto, __wsum csum)
  11{
  12
  13        int carry;
  14        __u32 ulen;
  15        __u32 uproto;
  16        __u32 sum = (__force u32)csum;
  17
  18        sum += (__force u32)saddr->s6_addr32[0];
  19        carry = (sum < (__force u32)saddr->s6_addr32[0]);
  20        sum += carry;
  21
  22        sum += (__force u32)saddr->s6_addr32[1];
  23        carry = (sum < (__force u32)saddr->s6_addr32[1]);
  24        sum += carry;
  25
  26        sum += (__force u32)saddr->s6_addr32[2];
  27        carry = (sum < (__force u32)saddr->s6_addr32[2]);
  28        sum += carry;
  29
  30        sum += (__force u32)saddr->s6_addr32[3];
  31        carry = (sum < (__force u32)saddr->s6_addr32[3]);
  32        sum += carry;
  33
  34        sum += (__force u32)daddr->s6_addr32[0];
  35        carry = (sum < (__force u32)daddr->s6_addr32[0]);
  36        sum += carry;
  37
  38        sum += (__force u32)daddr->s6_addr32[1];
  39        carry = (sum < (__force u32)daddr->s6_addr32[1]);
  40        sum += carry;
  41
  42        sum += (__force u32)daddr->s6_addr32[2];
  43        carry = (sum < (__force u32)daddr->s6_addr32[2]);
  44        sum += carry;
  45
  46        sum += (__force u32)daddr->s6_addr32[3];
  47        carry = (sum < (__force u32)daddr->s6_addr32[3]);
  48        sum += carry;
  49
  50        ulen = (__force u32)htonl((__u32) len);
  51        sum += ulen;
  52        carry = (sum < ulen);
  53        sum += carry;
  54
  55        uproto = (__force u32)htonl(proto);
  56        sum += uproto;
  57        carry = (sum < uproto);
  58        sum += carry;
  59
  60        return csum_fold((__force __wsum)sum);
  61}
  62EXPORT_SYMBOL(csum_ipv6_magic);
  63#endif
  64
  65int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
  66{
  67        int err;
  68
  69        UDP_SKB_CB(skb)->partial_cov = 0;
  70        UDP_SKB_CB(skb)->cscov = skb->len;
  71
  72        if (proto == IPPROTO_UDPLITE) {
  73                err = udplite_checksum_init(skb, uh);
  74                if (err)
  75                        return err;
  76
  77                if (UDP_SKB_CB(skb)->partial_cov) {
  78                        skb->csum = ip6_compute_pseudo(skb, proto);
  79                        return 0;
  80                }
  81        }
  82
  83        /* To support RFC 6936 (allow zero checksum in UDP/IPV6 for tunnels)
  84         * we accept a checksum of zero here. When we find the socket
  85         * for the UDP packet we'll check if that socket allows zero checksum
  86         * for IPv6 (set by socket option).
  87         *
  88         * Note, we are only interested in != 0 or == 0, thus the
  89         * force to int.
  90         */
  91        err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
  92                                                        ip6_compute_pseudo);
  93        if (err)
  94                return err;
  95
  96        if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
  97                /* If SW calculated the value, we know it's bad */
  98                if (skb->csum_complete_sw)
  99                        return 1;
 100
 101                /* HW says the value is bad. Let's validate that.
 102                 * skb->csum is no longer the full packet checksum,
 103                 * so don't treat is as such.
 104                 */
 105                skb_checksum_complete_unset(skb);
 106        }
 107
 108        return 0;
 109}
 110EXPORT_SYMBOL(udp6_csum_init);
 111
 112/* Function to set UDP checksum for an IPv6 UDP packet. This is intended
 113 * for the simple case like when setting the checksum for a UDP tunnel.
 114 */
 115void udp6_set_csum(bool nocheck, struct sk_buff *skb,
 116                   const struct in6_addr *saddr,
 117                   const struct in6_addr *daddr, int len)
 118{
 119        struct udphdr *uh = udp_hdr(skb);
 120
 121        if (nocheck)
 122                uh->check = 0;
 123        else if (skb_is_gso(skb))
 124                uh->check = ~udp_v6_check(len, saddr, daddr, 0);
 125        else if (skb->ip_summed == CHECKSUM_PARTIAL) {
 126                uh->check = 0;
 127                uh->check = udp_v6_check(len, saddr, daddr, lco_csum(skb));
 128                if (uh->check == 0)
 129                        uh->check = CSUM_MANGLED_0;
 130        } else {
 131                skb->ip_summed = CHECKSUM_PARTIAL;
 132                skb->csum_start = skb_transport_header(skb) - skb->head;
 133                skb->csum_offset = offsetof(struct udphdr, check);
 134                uh->check = ~udp_v6_check(len, saddr, daddr, 0);
 135        }
 136}
 137EXPORT_SYMBOL(udp6_set_csum);
 138