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