linux/net/ipv6/ila/ila_common.c
<<
>>
Prefs
   1#include <linux/errno.h>
   2#include <linux/ip.h>
   3#include <linux/kernel.h>
   4#include <linux/module.h>
   5#include <linux/skbuff.h>
   6#include <linux/socket.h>
   7#include <linux/types.h>
   8#include <net/checksum.h>
   9#include <net/ip.h>
  10#include <net/ip6_fib.h>
  11#include <net/lwtunnel.h>
  12#include <net/protocol.h>
  13#include <uapi/linux/ila.h>
  14#include "ila.h"
  15
  16void ila_init_saved_csum(struct ila_params *p)
  17{
  18        if (!p->locator_match.v64)
  19                return;
  20
  21        p->csum_diff = compute_csum_diff8(
  22                                (__be32 *)&p->locator,
  23                                (__be32 *)&p->locator_match);
  24}
  25
  26static __wsum get_csum_diff_iaddr(struct ila_addr *iaddr, struct ila_params *p)
  27{
  28        if (p->locator_match.v64)
  29                return p->csum_diff;
  30        else
  31                return compute_csum_diff8((__be32 *)&p->locator,
  32                                          (__be32 *)&iaddr->loc);
  33}
  34
  35static __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
  36{
  37        return get_csum_diff_iaddr(ila_a2i(&ip6h->daddr), p);
  38}
  39
  40static void ila_csum_do_neutral_fmt(struct ila_addr *iaddr,
  41                                    struct ila_params *p)
  42{
  43        __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
  44        __wsum diff, fval;
  45
  46        diff = get_csum_diff_iaddr(iaddr, p);
  47
  48        fval = (__force __wsum)(ila_csum_neutral_set(iaddr->ident) ?
  49                        CSUM_NEUTRAL_FLAG : ~CSUM_NEUTRAL_FLAG);
  50
  51        diff = csum_add(diff, fval);
  52
  53        *adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
  54
  55        /* Flip the csum-neutral bit. Either we are doing a SIR->ILA
  56         * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method
  57         * and the C-bit is not set, or we are doing an ILA-SIR
  58         * tranlsation and the C-bit is set.
  59         */
  60        iaddr->ident.csum_neutral ^= 1;
  61}
  62
  63static void ila_csum_do_neutral_nofmt(struct ila_addr *iaddr,
  64                                      struct ila_params *p)
  65{
  66        __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
  67        __wsum diff;
  68
  69        diff = get_csum_diff_iaddr(iaddr, p);
  70
  71        *adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
  72}
  73
  74static void ila_csum_adjust_transport(struct sk_buff *skb,
  75                                      struct ila_params *p)
  76{
  77        size_t nhoff = sizeof(struct ipv6hdr);
  78        struct ipv6hdr *ip6h = ipv6_hdr(skb);
  79        __wsum diff;
  80
  81        switch (ip6h->nexthdr) {
  82        case NEXTHDR_TCP:
  83                if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
  84                        struct tcphdr *th = (struct tcphdr *)
  85                                        (skb_network_header(skb) + nhoff);
  86
  87                        diff = get_csum_diff(ip6h, p);
  88                        inet_proto_csum_replace_by_diff(&th->check, skb,
  89                                                        diff, true);
  90                }
  91                break;
  92        case NEXTHDR_UDP:
  93                if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
  94                        struct udphdr *uh = (struct udphdr *)
  95                                        (skb_network_header(skb) + nhoff);
  96
  97                        if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
  98                                diff = get_csum_diff(ip6h, p);
  99                                inet_proto_csum_replace_by_diff(&uh->check, skb,
 100                                                                diff, true);
 101                                if (!uh->check)
 102                                        uh->check = CSUM_MANGLED_0;
 103                        }
 104                }
 105                break;
 106        case NEXTHDR_ICMP:
 107                if (likely(pskb_may_pull(skb,
 108                                         nhoff + sizeof(struct icmp6hdr)))) {
 109                        struct icmp6hdr *ih = (struct icmp6hdr *)
 110                                        (skb_network_header(skb) + nhoff);
 111
 112                        diff = get_csum_diff(ip6h, p);
 113                        inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
 114                                                        diff, true);
 115                }
 116                break;
 117        }
 118}
 119
 120void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
 121                             bool sir2ila)
 122{
 123        struct ipv6hdr *ip6h = ipv6_hdr(skb);
 124        struct ila_addr *iaddr = ila_a2i(&ip6h->daddr);
 125
 126        switch (p->csum_mode) {
 127        case ILA_CSUM_ADJUST_TRANSPORT:
 128                ila_csum_adjust_transport(skb, p);
 129                break;
 130        case ILA_CSUM_NEUTRAL_MAP:
 131                if (sir2ila) {
 132                        if (WARN_ON(ila_csum_neutral_set(iaddr->ident))) {
 133                                /* Checksum flag should never be
 134                                 * set in a formatted SIR address.
 135                                 */
 136                                break;
 137                        }
 138                } else if (!ila_csum_neutral_set(iaddr->ident)) {
 139                        /* ILA to SIR translation and C-bit isn't
 140                         * set so we're good.
 141                         */
 142                        break;
 143                }
 144                ila_csum_do_neutral_fmt(iaddr, p);
 145                break;
 146        case ILA_CSUM_NEUTRAL_MAP_AUTO:
 147                ila_csum_do_neutral_nofmt(iaddr, p);
 148                break;
 149        case ILA_CSUM_NO_ACTION:
 150                break;
 151        }
 152
 153        /* Now change destination address */
 154        iaddr->loc = p->locator;
 155}
 156