170583Sobrien#include <linux/errno.h>
2178628Smarcel#include <linux/ip.h>
370583Sobrien#include <linux/kernel.h>
470583Sobrien#include <linux/module.h>
570583Sobrien#include <linux/skbuff.h>
670583Sobrien#include <linux/socket.h>
770583Sobrien#include <linux/types.h>
8178628Smarcel#include <net/checksum.h>
970583Sobrien#include <net/ip.h>
1070583Sobrien#include <net/ip6_fib.h>
1170583Sobrien#include <net/lwtunnel.h>
1270583Sobrien#include <net/protocol.h>
1370583Sobrien#include <uapi/linux/ila.h>
1470583Sobrien#include "ila.h"
15178628Smarcel
16178628Smarcelvoid ila_init_saved_csum(struct ila_params *p)
17178628Smarcel{
18178628Smarcel	if (!p->locator_match.v64)
19178628Smarcel		return;
20178628Smarcel
21178628Smarcel	p->csum_diff = compute_csum_diff8(
22178628Smarcel				(__be32 *)&p->locator,
23178628Smarcel				(__be32 *)&p->locator_match);
24178628Smarcel}
2570583Sobrien
2670583Sobrienstatic __wsum get_csum_diff_iaddr(struct ila_addr *iaddr, struct ila_params *p)
27178628Smarcel{
28178628Smarcel	if (p->locator_match.v64)
29178628Smarcel		return p->csum_diff;
3070583Sobrien	else
3170583Sobrien		return compute_csum_diff8((__be32 *)&p->locator,
32178628Smarcel					  (__be32 *)&iaddr->loc);
33192109Sraj}
34178628Smarcel
35212170Sgrehanstatic __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
36212170Sgrehan{
37178628Smarcel	return get_csum_diff_iaddr(ila_a2i(&ip6h->daddr), p);
3870583Sobrien}
39178628Smarcel
4076078Sjhbstatic void ila_csum_do_neutral_fmt(struct ila_addr *iaddr,
4170583Sobrien				    struct ila_params *p)
42194933Sjeff{
43194933Sjeff	__sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
44194933Sjeff	__wsum diff, fval;
45194933Sjeff
46194933Sjeff	diff = get_csum_diff_iaddr(iaddr, p);
47194933Sjeff
48194933Sjeff	fval = (__force __wsum)(ila_csum_neutral_set(iaddr->ident) ?
49178628Smarcel			CSUM_NEUTRAL_FLAG : ~CSUM_NEUTRAL_FLAG);
50178628Smarcel
51178628Smarcel	diff = csum_add(diff, fval);
52192067Snwhitehorn
53192109Sraj	*adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
54178628Smarcel
5570583Sobrien	/* Flip the csum-neutral bit. Either we are doing a SIR->ILA
56178628Smarcel	 * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method
5770583Sobrien	 * and the C-bit is not set, or we are doing an ILA-SIR
58178628Smarcel	 * tranlsation and the C-bit is set.
5970583Sobrien	 */
60178628Smarcel	iaddr->ident.csum_neutral ^= 1;
61192109Sraj}
62192109Sraj
63192109Srajstatic void ila_csum_do_neutral_nofmt(struct ila_addr *iaddr,
64212170Sgrehan				      struct ila_params *p)
65178628Smarcel{
66178628Smarcel	__sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
67183060Smarcel	__wsum diff;
68178628Smarcel
69198378Snwhitehorn	diff = get_csum_diff_iaddr(iaddr, p);
70198378Snwhitehorn
71178628Smarcel	*adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
72198378Snwhitehorn}
73192109Sraj
74192109Srajstatic void ila_csum_adjust_transport(struct sk_buff *skb,
75192109Sraj				      struct ila_params *p)
76178628Smarcel{
77192109Sraj	size_t nhoff = sizeof(struct ipv6hdr);
78178628Smarcel	struct ipv6hdr *ip6h = ipv6_hdr(skb);
79178628Smarcel	__wsum diff;
80192109Sraj
81215160Snwhitehorn	switch (ip6h->nexthdr) {
82215160Snwhitehorn	case NEXTHDR_TCP:
83215160Snwhitehorn		if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
84215160Snwhitehorn			struct tcphdr *th = (struct tcphdr *)
85215160Snwhitehorn					(skb_network_header(skb) + nhoff);
86212453Smav
87215160Snwhitehorn			diff = get_csum_diff(ip6h, p);
88192109Sraj			inet_proto_csum_replace_by_diff(&th->check, skb,
89178628Smarcel							diff, true);
90212170Sgrehan		}
91212170Sgrehan		break;
92212170Sgrehan	case NEXTHDR_UDP:
93198378Snwhitehorn		if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
94212170Sgrehan			struct udphdr *uh = (struct udphdr *)
95178628Smarcel					(skb_network_header(skb) + nhoff);
96192109Sraj
97178628Smarcel			if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
98183083Smarcel				diff = get_csum_diff(ip6h, p);
99178628Smarcel				inet_proto_csum_replace_by_diff(&uh->check, skb,
100212556Smav								diff, true);
101212556Smav				if (!uh->check)
102212556Smav					uh->check = CSUM_MANGLED_0;
103198378Snwhitehorn			}
104178628Smarcel		}
105178628Smarcel		break;
106178628Smarcel	case NEXTHDR_ICMP:
107122947Sjhb		if (likely(pskb_may_pull(skb,
108122947Sjhb					 nhoff + sizeof(struct icmp6hdr)))) {
109122947Sjhb			struct icmp6hdr *ih = (struct icmp6hdr *)
110178628Smarcel					(skb_network_header(skb) + nhoff);
111178628Smarcel
112178628Smarcel			diff = get_csum_diff(ip6h, p);
113178628Smarcel			inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
114192067Snwhitehorn							diff, true);
115178628Smarcel		}
116178628Smarcel		break;
117192067Snwhitehorn	}
118178628Smarcel}
119178628Smarcel
120178628Smarcelvoid ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
121178628Smarcel			     bool sir2ila)
122178628Smarcel{
123178628Smarcel	struct ipv6hdr *ip6h = ipv6_hdr(skb);
124178628Smarcel	struct ila_addr *iaddr = ila_a2i(&ip6h->daddr);
125178628Smarcel
126178628Smarcel	switch (p->csum_mode) {
127178628Smarcel	case ILA_CSUM_ADJUST_TRANSPORT:
128122947Sjhb		ila_csum_adjust_transport(skb, p);
129122947Sjhb		break;
13070583Sobrien	case ILA_CSUM_NEUTRAL_MAP:
13176442Sjhb		if (sir2ila) {
13270583Sobrien			if (WARN_ON(ila_csum_neutral_set(iaddr->ident))) {
133178628Smarcel				/* Checksum flag should never be
134178628Smarcel				 * set in a formatted SIR address.
135178628Smarcel				 */
136178628Smarcel				break;
137178628Smarcel			}
13870583Sobrien		} else if (!ila_csum_neutral_set(iaddr->ident)) {
13970583Sobrien			/* ILA to SIR translation and C-bit isn't
14070583Sobrien			 * set so we're good.
14176442Sjhb			 */
14270583Sobrien			break;
143178628Smarcel		}
144178628Smarcel		ila_csum_do_neutral_fmt(iaddr, p);
145178628Smarcel		break;
146178628Smarcel	case ILA_CSUM_NEUTRAL_MAP_AUTO:
147192067Snwhitehorn		ila_csum_do_neutral_nofmt(iaddr, p);
148178628Smarcel		break;
149178628Smarcel	case ILA_CSUM_NO_ACTION:
150178628Smarcel		break;
151192067Snwhitehorn	}
152178628Smarcel
153178628Smarcel	/* Now change destination address */
154178628Smarcel	iaddr->loc = p->locator;
155178628Smarcel}
156178628Smarcel