xref: /openbmc/linux/net/ipv6/ila/ila_common.c (revision 3eb66e91a25497065c5322b1268cbc3953642227)
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