1 | #include <linux/errno.h> |
2 | #include <linux/ip.h> |
3 | #include <linux/kernel.h> |
4 | #include <linux/module.h> |
5 | #include <linux/skbuff.h> |
6 | #include <linux/socket.h> |
7 | #include <linux/types.h> |
8 | #include <net/checksum.h> |
9 | #include <net/ip.h> |
10 | #include <net/ip6_fib.h> |
11 | #include <net/lwtunnel.h> |
12 | #include <net/protocol.h> |
13 | #include <uapi/linux/ila.h> |
14 | #include "ila.h" |
15 | |
16 | void ila_init_saved_csum(struct ila_params *p) |
17 | { |
18 | if (!p->locator_match.v64) |
19 | return; |
20 | |
21 | p->csum_diff = compute_csum_diff8( |
22 | from: (__be32 *)&p->locator, |
23 | to: (__be32 *)&p->locator_match); |
24 | } |
25 | |
26 | static __wsum get_csum_diff_iaddr(struct ila_addr *iaddr, struct ila_params *p) |
27 | { |
28 | if (p->locator_match.v64) |
29 | return p->csum_diff; |
30 | else |
31 | return compute_csum_diff8(from: (__be32 *)&p->locator, |
32 | to: (__be32 *)&iaddr->loc); |
33 | } |
34 | |
35 | static __wsum get_csum_diff(struct ipv6hdr *ip6h, struct ila_params *p) |
36 | { |
37 | return get_csum_diff_iaddr(iaddr: ila_a2i(addr: &ip6h->daddr), p); |
38 | } |
39 | |
40 | static void ila_csum_do_neutral_fmt(struct ila_addr *iaddr, |
41 | struct ila_params *p) |
42 | { |
43 | __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3]; |
44 | __wsum diff, fval; |
45 | |
46 | diff = get_csum_diff_iaddr(iaddr, p); |
47 | |
48 | fval = (__force __wsum)(ila_csum_neutral_set(ident: iaddr->ident) ? |
49 | CSUM_NEUTRAL_FLAG : ~CSUM_NEUTRAL_FLAG); |
50 | |
51 | diff = csum_add(csum: diff, addend: fval); |
52 | |
53 | *adjust = ~csum_fold(sum: csum_add(csum: diff, addend: csum_unfold(n: *adjust))); |
54 | |
55 | /* Flip the csum-neutral bit. Either we are doing a SIR->ILA |
56 | * translation with ILA_CSUM_NEUTRAL_MAP as the csum_method |
57 | * and the C-bit is not set, or we are doing an ILA-SIR |
58 | * tranlsation and the C-bit is set. |
59 | */ |
60 | iaddr->ident.csum_neutral ^= 1; |
61 | } |
62 | |
63 | static void ila_csum_do_neutral_nofmt(struct ila_addr *iaddr, |
64 | struct ila_params *p) |
65 | { |
66 | __sum16 *adjust = (__force __sum16 *)&iaddr->ident.v16[3]; |
67 | __wsum diff; |
68 | |
69 | diff = get_csum_diff_iaddr(iaddr, p); |
70 | |
71 | *adjust = ~csum_fold(sum: csum_add(csum: diff, addend: csum_unfold(n: *adjust))); |
72 | } |
73 | |
74 | static void ila_csum_adjust_transport(struct sk_buff *skb, |
75 | struct ila_params *p) |
76 | { |
77 | size_t nhoff = sizeof(struct ipv6hdr); |
78 | struct ipv6hdr *ip6h = ipv6_hdr(skb); |
79 | __wsum diff; |
80 | |
81 | switch (ip6h->nexthdr) { |
82 | case NEXTHDR_TCP: |
83 | if (likely(pskb_may_pull(skb, nhoff + sizeof(struct tcphdr)))) { |
84 | struct tcphdr *th = (struct tcphdr *) |
85 | (skb_network_header(skb) + nhoff); |
86 | |
87 | diff = get_csum_diff(ip6h, p); |
88 | inet_proto_csum_replace_by_diff(sum: &th->check, skb, |
89 | diff, pseudohdr: true); |
90 | } |
91 | break; |
92 | case NEXTHDR_UDP: |
93 | if (likely(pskb_may_pull(skb, nhoff + sizeof(struct udphdr)))) { |
94 | struct udphdr *uh = (struct udphdr *) |
95 | (skb_network_header(skb) + nhoff); |
96 | |
97 | if (uh->check || skb->ip_summed == CHECKSUM_PARTIAL) { |
98 | diff = get_csum_diff(ip6h, p); |
99 | inet_proto_csum_replace_by_diff(sum: &uh->check, skb, |
100 | diff, pseudohdr: true); |
101 | if (!uh->check) |
102 | uh->check = CSUM_MANGLED_0; |
103 | } |
104 | } |
105 | break; |
106 | case NEXTHDR_ICMP: |
107 | if (likely(pskb_may_pull(skb, |
108 | nhoff + sizeof(struct icmp6hdr)))) { |
109 | struct icmp6hdr *ih = (struct icmp6hdr *) |
110 | (skb_network_header(skb) + nhoff); |
111 | |
112 | diff = get_csum_diff(ip6h, p); |
113 | inet_proto_csum_replace_by_diff(sum: &ih->icmp6_cksum, skb, |
114 | diff, pseudohdr: true); |
115 | } |
116 | break; |
117 | } |
118 | } |
119 | |
120 | void ila_update_ipv6_locator(struct sk_buff *skb, struct ila_params *p, |
121 | bool sir2ila) |
122 | { |
123 | struct ipv6hdr *ip6h = ipv6_hdr(skb); |
124 | struct ila_addr *iaddr = ila_a2i(addr: &ip6h->daddr); |
125 | |
126 | switch (p->csum_mode) { |
127 | case ILA_CSUM_ADJUST_TRANSPORT: |
128 | ila_csum_adjust_transport(skb, p); |
129 | break; |
130 | case ILA_CSUM_NEUTRAL_MAP: |
131 | if (sir2ila) { |
132 | if (WARN_ON(ila_csum_neutral_set(iaddr->ident))) { |
133 | /* Checksum flag should never be |
134 | * set in a formatted SIR address. |
135 | */ |
136 | break; |
137 | } |
138 | } else if (!ila_csum_neutral_set(ident: iaddr->ident)) { |
139 | /* ILA to SIR translation and C-bit isn't |
140 | * set so we're good. |
141 | */ |
142 | break; |
143 | } |
144 | ila_csum_do_neutral_fmt(iaddr, p); |
145 | break; |
146 | case ILA_CSUM_NEUTRAL_MAP_AUTO: |
147 | ila_csum_do_neutral_nofmt(iaddr, p); |
148 | break; |
149 | case ILA_CSUM_NO_ACTION: |
150 | break; |
151 | } |
152 | |
153 | /* Now change destination address */ |
154 | iaddr->loc = p->locator; |
155 | } |
156 | |