18c2ecf20Sopenharmony_ci#include <linux/errno.h>
28c2ecf20Sopenharmony_ci#include <linux/ip.h>
38c2ecf20Sopenharmony_ci#include <linux/kernel.h>
48c2ecf20Sopenharmony_ci#include <linux/module.h>
58c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
68c2ecf20Sopenharmony_ci#include <linux/socket.h>
78c2ecf20Sopenharmony_ci#include <linux/types.h>
88c2ecf20Sopenharmony_ci#include <net/checksum.h>
98c2ecf20Sopenharmony_ci#include <net/ip.h>
108c2ecf20Sopenharmony_ci#include <net/ip6_fib.h>
118c2ecf20Sopenharmony_ci#include <net/lwtunnel.h>
128c2ecf20Sopenharmony_ci#include <net/protocol.h>
138c2ecf20Sopenharmony_ci#include <uapi/linux/ila.h>
148c2ecf20Sopenharmony_ci#include "ila.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_civoid ila_init_saved_csum(struct ila_params *p)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	if (!p->locator_match.v64)
198c2ecf20Sopenharmony_ci		return;
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci	p->csum_diff = compute_csum_diff8(
228c2ecf20Sopenharmony_ci				(__be32 *)&p->locator,
238c2ecf20Sopenharmony_ci				(__be32 *)&p->locator_match);
248c2ecf20Sopenharmony_ci}
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic __wsum get_csum_diff_iaddr(struct ila_addr *iaddr, struct ila_params *p)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	if (p->locator_match.v64)
298c2ecf20Sopenharmony_ci		return p->csum_diff;
308c2ecf20Sopenharmony_ci	else
318c2ecf20Sopenharmony_ci		return compute_csum_diff8((__be32 *)&p->locator,
328c2ecf20Sopenharmony_ci					  (__be32 *)&iaddr->loc);
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p)
368c2ecf20Sopenharmony_ci{
378c2ecf20Sopenharmony_ci	return get_csum_diff_iaddr(ila_a2i(&ip6h->daddr), p);
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic void ila_csum_do_neutral_fmt(struct ila_addr *iaddr,
418c2ecf20Sopenharmony_ci				    struct ila_params *p)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	__sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
448c2ecf20Sopenharmony_ci	__wsum diff, fval;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	diff = get_csum_diff_iaddr(iaddr, p);
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	fval = (__force __wsum)(ila_csum_neutral_set(iaddr->ident) ?
498c2ecf20Sopenharmony_ci			CSUM_NEUTRAL_FLAG : ~CSUM_NEUTRAL_FLAG);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	diff = csum_add(diff, fval);
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	*adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	/* Flip the csum-neutral bit. Either we are doing a SIR->ILA
568c2ecf20Sopenharmony_ci	 * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method
578c2ecf20Sopenharmony_ci	 * and the C-bit is not set, or we are doing an ILA-SIR
588c2ecf20Sopenharmony_ci	 * tranlsation and the C-bit is set.
598c2ecf20Sopenharmony_ci	 */
608c2ecf20Sopenharmony_ci	iaddr->ident.csum_neutral ^= 1;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic void ila_csum_do_neutral_nofmt(struct ila_addr *iaddr,
648c2ecf20Sopenharmony_ci				      struct ila_params *p)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	__sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3];
678c2ecf20Sopenharmony_ci	__wsum diff;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	diff = get_csum_diff_iaddr(iaddr, p);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	*adjust = ~csum_fold(csum_add(diff, csum_unfold(*adjust)));
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic void ila_csum_adjust_transport(struct sk_buff *skb,
758c2ecf20Sopenharmony_ci				      struct ila_params *p)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	size_t nhoff = sizeof(struct ipv6hdr);
788c2ecf20Sopenharmony_ci	struct ipv6hdr *ip6h = ipv6_hdr(skb);
798c2ecf20Sopenharmony_ci	__wsum diff;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	switch (ip6h->nexthdr) {
828c2ecf20Sopenharmony_ci	case NEXTHDR_TCP:
838c2ecf20Sopenharmony_ci		if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) {
848c2ecf20Sopenharmony_ci			struct tcphdr *th = (struct tcphdr *)
858c2ecf20Sopenharmony_ci					(skb_network_header(skb) + nhoff);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci			diff = get_csum_diff(ip6h, p);
888c2ecf20Sopenharmony_ci			inet_proto_csum_replace_by_diff(&th->check, skb,
898c2ecf20Sopenharmony_ci							diff, true);
908c2ecf20Sopenharmony_ci		}
918c2ecf20Sopenharmony_ci		break;
928c2ecf20Sopenharmony_ci	case NEXTHDR_UDP:
938c2ecf20Sopenharmony_ci		if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) {
948c2ecf20Sopenharmony_ci			struct udphdr *uh = (struct udphdr *)
958c2ecf20Sopenharmony_ci					(skb_network_header(skb) + nhoff);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci			if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) {
988c2ecf20Sopenharmony_ci				diff = get_csum_diff(ip6h, p);
998c2ecf20Sopenharmony_ci				inet_proto_csum_replace_by_diff(&uh->check, skb,
1008c2ecf20Sopenharmony_ci								diff, true);
1018c2ecf20Sopenharmony_ci				if (!uh->check)
1028c2ecf20Sopenharmony_ci					uh->check = CSUM_MANGLED_0;
1038c2ecf20Sopenharmony_ci			}
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci		break;
1068c2ecf20Sopenharmony_ci	case NEXTHDR_ICMP:
1078c2ecf20Sopenharmony_ci		if (likely(pskb_may_pull(skb,
1088c2ecf20Sopenharmony_ci					 nhoff + sizeof(struct icmp6hdr)))) {
1098c2ecf20Sopenharmony_ci			struct icmp6hdr *ih = (struct icmp6hdr *)
1108c2ecf20Sopenharmony_ci					(skb_network_header(skb) + nhoff);
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci			diff = get_csum_diff(ip6h, p);
1138c2ecf20Sopenharmony_ci			inet_proto_csum_replace_by_diff(&ih->icmp6_cksum, skb,
1148c2ecf20Sopenharmony_ci							diff, true);
1158c2ecf20Sopenharmony_ci		}
1168c2ecf20Sopenharmony_ci		break;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_civoid ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p,
1218c2ecf20Sopenharmony_ci			     bool sir2ila)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct ipv6hdr *ip6h = ipv6_hdr(skb);
1248c2ecf20Sopenharmony_ci	struct ila_addr *iaddr = ila_a2i(&ip6h->daddr);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	switch (p->csum_mode) {
1278c2ecf20Sopenharmony_ci	case ILA_CSUM_ADJUST_TRANSPORT:
1288c2ecf20Sopenharmony_ci		ila_csum_adjust_transport(skb, p);
1298c2ecf20Sopenharmony_ci		break;
1308c2ecf20Sopenharmony_ci	case ILA_CSUM_NEUTRAL_MAP:
1318c2ecf20Sopenharmony_ci		if (sir2ila) {
1328c2ecf20Sopenharmony_ci			if (WARN_ON(ila_csum_neutral_set(iaddr->ident))) {
1338c2ecf20Sopenharmony_ci				/* Checksum flag should never be
1348c2ecf20Sopenharmony_ci				 * set in a formatted SIR address.
1358c2ecf20Sopenharmony_ci				 */
1368c2ecf20Sopenharmony_ci				break;
1378c2ecf20Sopenharmony_ci			}
1388c2ecf20Sopenharmony_ci		} else if (!ila_csum_neutral_set(iaddr->ident)) {
1398c2ecf20Sopenharmony_ci			/* ILA to SIR translation and C-bit isn't
1408c2ecf20Sopenharmony_ci			 * set so we're good.
1418c2ecf20Sopenharmony_ci			 */
1428c2ecf20Sopenharmony_ci			break;
1438c2ecf20Sopenharmony_ci		}
1448c2ecf20Sopenharmony_ci		ila_csum_do_neutral_fmt(iaddr, p);
1458c2ecf20Sopenharmony_ci		break;
1468c2ecf20Sopenharmony_ci	case ILA_CSUM_NEUTRAL_MAP_AUTO:
1478c2ecf20Sopenharmony_ci		ila_csum_do_neutral_nofmt(iaddr, p);
1488c2ecf20Sopenharmony_ci		break;
1498c2ecf20Sopenharmony_ci	case ILA_CSUM_NO_ACTION:
1508c2ecf20Sopenharmony_ci		break;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	/* Now change destination address */
1548c2ecf20Sopenharmony_ci	iaddr->loc = p->locator;
1558c2ecf20Sopenharmony_ci}
156