133f11d16STom Herbert #include <linux/errno.h>
233f11d16STom Herbert #include <linux/ip.h>
333f11d16STom Herbert #include <linux/kernel.h>
433f11d16STom Herbert #include <linux/module.h>
533f11d16STom Herbert #include <linux/skbuff.h>
633f11d16STom Herbert #include <linux/socket.h>
733f11d16STom Herbert #include <linux/types.h>
833f11d16STom Herbert #include <net/checksum.h>
933f11d16STom Herbert #include <net/ip.h>
1033f11d16STom Herbert #include <net/ip6_fib.h>
1133f11d16STom Herbert #include <net/lwtunnel.h>
1233f11d16STom Herbert #include <net/protocol.h>
1333f11d16STom Herbert #include <uapi/linux/ila.h>
1433f11d16STom Herbert #include "ila.h"
1533f11d16STom Herbert
ila_init_saved_csum(struct ila_params * p)1680661e76STom Herbert void ila_init_saved_csum(struct ila_params *p)
1733f11d16STom Herbert {
1880661e76STom Herbert if (!p->locator_match.v64)
1980661e76STom Herbert return;
20351596aaSTom Herbert
2180661e76STom Herbert p->csum_diff = compute_csum_diff8(
2280661e76STom Herbert (__be32 *)&p->locator,
2380661e76STom Herbert (__be32 *)&p->locator_match);
2480661e76STom Herbert }
2580661e76STom Herbert
get_csum_diff_iaddr(struct ila_addr * iaddr,struct ila_params * p)2680661e76STom Herbert static __wsum get_csum_diff_iaddr(struct ila_addr *iaddr, struct ila_params *p)
2780661e76STom Herbert {
2890bfe662STom Herbert if (p->locator_match.v64)
2933f11d16STom Herbert return p->csum_diff;
3033f11d16STom Herbert else
3180661e76STom Herbert return compute_csum_diff8((__be32 *)&p->locator,
3280661e76STom Herbert (__be32 *)&iaddr->loc);
3380661e76STom Herbert }
3480661e76STom Herbert
get_csum_diff(struct ipv6hdr * ip6h,struct ila_params * p)3580661e76STom Herbert static __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
3680661e76STom Herbert {
3780661e76STom Herbert return get_csum_diff_iaddr(ila_a2i(&ip6h->daddr), p);
3833f11d16STom Herbert }
3933f11d16STom Herbert
ila_csum_do_neutral_fmt(struct ila_addr * iaddr,struct ila_params * p)40*84287bb3STom Herbert static void ila_csum_do_neutral_fmt(struct ila_addr *iaddr,
4190bfe662STom Herbert struct ila_params *p)
4290bfe662STom Herbert {
4390bfe662STom Herbert __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
4490bfe662STom Herbert __wsum diff, fval;
4590bfe662STom Herbert
4680661e76STom Herbert diff = get_csum_diff_iaddr(iaddr, p);
4790bfe662STom Herbert
4890bfe662STom Herbert fval = (__force __wsum)(ila_csum_neutral_set(iaddr->ident) ?
490b797c85STom Herbert CSUM_NEUTRAL_FLAG : ~CSUM_NEUTRAL_FLAG);
5090bfe662STom Herbert
5190bfe662STom Herbert diff = csum_add(diff, fval);
5290bfe662STom Herbert
5390bfe662STom Herbert *adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
5490bfe662STom Herbert
5590bfe662STom Herbert /* Flip the csum-neutral bit. Either we are doing a SIR->ILA
5690bfe662STom Herbert * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method
5790bfe662STom Herbert * and the C-bit is not set, or we are doing an ILA-SIR
5890bfe662STom Herbert * tranlsation and the C-bit is set.
5990bfe662STom Herbert */
6090bfe662STom Herbert iaddr->ident.csum_neutral ^= 1;
6190bfe662STom Herbert }
6290bfe662STom Herbert
ila_csum_do_neutral_nofmt(struct ila_addr * iaddr,struct ila_params * p)63*84287bb3STom Herbert static void ila_csum_do_neutral_nofmt(struct ila_addr *iaddr,
64*84287bb3STom Herbert struct ila_params *p)
65*84287bb3STom Herbert {
66*84287bb3STom Herbert __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
67*84287bb3STom Herbert __wsum diff;
68*84287bb3STom Herbert
69*84287bb3STom Herbert diff = get_csum_diff_iaddr(iaddr, p);
70*84287bb3STom Herbert
71*84287bb3STom Herbert *adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
72*84287bb3STom Herbert }
73*84287bb3STom Herbert
ila_csum_adjust_transport(struct sk_buff * skb,struct ila_params * p)7490bfe662STom Herbert static void ila_csum_adjust_transport(struct sk_buff *skb,
7590bfe662STom Herbert struct ila_params *p)
7633f11d16STom Herbert {
7733f11d16STom Herbert size_t nhoff = sizeof(struct ipv6hdr);
78*84287bb3STom Herbert struct ipv6hdr *ip6h = ipv6_hdr(skb);
79*84287bb3STom Herbert __wsum diff;
8033f11d16STom Herbert
8133f11d16STom Herbert switch (ip6h->nexthdr) {
8233f11d16STom Herbert case NEXTHDR_TCP:
8333f11d16STom Herbert if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
8433f11d16STom Herbert struct tcphdr *th = (struct tcphdr *)
8533f11d16STom Herbert (skb_network_header(skb) + nhoff);
8633f11d16STom Herbert
8733f11d16STom Herbert diff = get_csum_diff(ip6h, p);
8833f11d16STom Herbert inet_proto_csum_replace_by_diff(&th->check, skb,
8933f11d16STom Herbert diff, true);
9033f11d16STom Herbert }
9133f11d16STom Herbert break;
9233f11d16STom Herbert case NEXTHDR_UDP:
9333f11d16STom Herbert if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
9433f11d16STom Herbert struct udphdr *uh = (struct udphdr *)
9533f11d16STom Herbert (skb_network_header(skb) + nhoff);
9633f11d16STom Herbert
9733f11d16STom Herbert if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
9833f11d16STom Herbert diff = get_csum_diff(ip6h, p);
9933f11d16STom Herbert inet_proto_csum_replace_by_diff(&uh->check, skb,
10033f11d16STom Herbert diff, true);
10133f11d16STom Herbert if (!uh->check)
10233f11d16STom Herbert uh->check = CSUM_MANGLED_0;
10333f11d16STom Herbert }
10433f11d16STom Herbert }
10533f11d16STom Herbert break;
10633f11d16STom Herbert case NEXTHDR_ICMP:
10733f11d16STom Herbert if (likely(pskb_may_pull(skb,
10833f11d16STom Herbert nhoff + sizeof(struct icmp6hdr)))) {
10933f11d16STom Herbert struct icmp6hdr *ih = (struct icmp6hdr *)
11033f11d16STom Herbert (skb_network_header(skb) + nhoff);
11133f11d16STom Herbert
11233f11d16STom Herbert diff = get_csum_diff(ip6h, p);
11333f11d16STom Herbert inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
11433f11d16STom Herbert diff, true);
11533f11d16STom Herbert }
11633f11d16STom Herbert break;
11733f11d16STom Herbert }
11833f11d16STom Herbert }
11933f11d16STom Herbert
ila_update_ipv6_locator(struct sk_buff * skb,struct ila_params * p,bool sir2ila)120707a2ca4STom Herbert void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
121*84287bb3STom Herbert bool sir2ila)
12290bfe662STom Herbert {
12390bfe662STom Herbert struct ipv6hdr *ip6h = ipv6_hdr(skb);
12490bfe662STom Herbert struct ila_addr *iaddr = ila_a2i(&ip6h->daddr);
12590bfe662STom Herbert
12690bfe662STom Herbert switch (p->csum_mode) {
12790bfe662STom Herbert case ILA_CSUM_ADJUST_TRANSPORT:
12890bfe662STom Herbert ila_csum_adjust_transport(skb, p);
12990bfe662STom Herbert break;
13090bfe662STom Herbert case ILA_CSUM_NEUTRAL_MAP:
131*84287bb3STom Herbert if (sir2ila) {
132*84287bb3STom Herbert if (WARN_ON(ila_csum_neutral_set(iaddr->ident))) {
133*84287bb3STom Herbert /* Checksum flag should never be
134*84287bb3STom Herbert * set in a formatted SIR address.
135*84287bb3STom Herbert */
136*84287bb3STom Herbert break;
137*84287bb3STom Herbert }
138*84287bb3STom Herbert } else if (!ila_csum_neutral_set(iaddr->ident)) {
139*84287bb3STom Herbert /* ILA to SIR translation and C-bit isn't
140*84287bb3STom Herbert * set so we're good.
141*84287bb3STom Herbert */
142*84287bb3STom Herbert break;
143*84287bb3STom Herbert }
144*84287bb3STom Herbert ila_csum_do_neutral_fmt(iaddr, p);
145*84287bb3STom Herbert break;
146*84287bb3STom Herbert case ILA_CSUM_NEUTRAL_MAP_AUTO:
147*84287bb3STom Herbert ila_csum_do_neutral_nofmt(iaddr, p);
14890bfe662STom Herbert break;
14990bfe662STom Herbert case ILA_CSUM_NO_ACTION:
15090bfe662STom Herbert break;
15190bfe662STom Herbert }
15290bfe662STom Herbert
15390bfe662STom Herbert /* Now change destination address */
15490bfe662STom Herbert iaddr->loc = p->locator;
15590bfe662STom Herbert }
156