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