1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <linux/errno.h> |
3 | #include <linux/ip.h> |
4 | #include <linux/kernel.h> |
5 | #include <linux/module.h> |
6 | #include <linux/skbuff.h> |
7 | #include <linux/socket.h> |
8 | #include <linux/types.h> |
9 | #include <net/checksum.h> |
10 | #include <net/dst_cache.h> |
11 | #include <net/ip.h> |
12 | #include <net/ip6_fib.h> |
13 | #include <net/ip6_route.h> |
14 | #include <net/lwtunnel.h> |
15 | #include <net/protocol.h> |
16 | #include <uapi/linux/ila.h> |
17 | #include "ila.h" |
18 | |
19 | struct ila_lwt { |
20 | struct ila_params p; |
21 | struct dst_cache dst_cache; |
22 | u32 connected : 1; |
23 | u32 lwt_output : 1; |
24 | }; |
25 | |
26 | static inline struct ila_lwt *ila_lwt_lwtunnel( |
27 | struct lwtunnel_state *lwt) |
28 | { |
29 | return (struct ila_lwt *)lwt->data; |
30 | } |
31 | |
32 | static inline struct ila_params *ila_params_lwtunnel( |
33 | struct lwtunnel_state *lwt) |
34 | { |
35 | return &ila_lwt_lwtunnel(lwt)->p; |
36 | } |
37 | |
38 | static int ila_output(struct net *net, struct sock *sk, struct sk_buff *skb) |
39 | { |
40 | struct dst_entry *orig_dst = skb_dst(skb); |
41 | struct rt6_info *rt = (struct rt6_info *)orig_dst; |
42 | struct ila_lwt *ilwt = ila_lwt_lwtunnel(lwt: orig_dst->lwtstate); |
43 | struct dst_entry *dst; |
44 | int err = -EINVAL; |
45 | |
46 | if (skb->protocol != htons(ETH_P_IPV6)) |
47 | goto drop; |
48 | |
49 | if (ilwt->lwt_output) |
50 | ila_update_ipv6_locator(skb, |
51 | p: ila_params_lwtunnel(lwt: orig_dst->lwtstate), |
52 | set_csum_neutral: true); |
53 | |
54 | if (rt->rt6i_flags & (RTF_GATEWAY | RTF_CACHE)) { |
55 | /* Already have a next hop address in route, no need for |
56 | * dest cache route. |
57 | */ |
58 | return orig_dst->lwtstate->orig_output(net, sk, skb); |
59 | } |
60 | |
61 | dst = dst_cache_get(dst_cache: &ilwt->dst_cache); |
62 | if (unlikely(!dst)) { |
63 | struct ipv6hdr *ip6h = ipv6_hdr(skb); |
64 | struct flowi6 fl6; |
65 | |
66 | /* Lookup a route for the new destination. Take into |
67 | * account that the base route may already have a gateway. |
68 | */ |
69 | |
70 | memset(&fl6, 0, sizeof(fl6)); |
71 | fl6.flowi6_oif = orig_dst->dev->ifindex; |
72 | fl6.flowi6_iif = LOOPBACK_IFINDEX; |
73 | fl6.daddr = *rt6_nexthop(rt: (struct rt6_info *)orig_dst, |
74 | daddr: &ip6h->daddr); |
75 | |
76 | dst = ip6_route_output(net, NULL, fl6: &fl6); |
77 | if (dst->error) { |
78 | err = -EHOSTUNREACH; |
79 | dst_release(dst); |
80 | goto drop; |
81 | } |
82 | |
83 | dst = xfrm_lookup(net, dst_orig: dst, fl: flowi6_to_flowi(fl6: &fl6), NULL, flags: 0); |
84 | if (IS_ERR(ptr: dst)) { |
85 | err = PTR_ERR(ptr: dst); |
86 | goto drop; |
87 | } |
88 | |
89 | if (ilwt->connected) |
90 | dst_cache_set_ip6(dst_cache: &ilwt->dst_cache, dst, saddr: &fl6.saddr); |
91 | } |
92 | |
93 | skb_dst_set(skb, dst); |
94 | return dst_output(net, sk, skb); |
95 | |
96 | drop: |
97 | kfree_skb(skb); |
98 | return err; |
99 | } |
100 | |
101 | static int ila_input(struct sk_buff *skb) |
102 | { |
103 | struct dst_entry *dst = skb_dst(skb); |
104 | struct ila_lwt *ilwt = ila_lwt_lwtunnel(lwt: dst->lwtstate); |
105 | |
106 | if (skb->protocol != htons(ETH_P_IPV6)) |
107 | goto drop; |
108 | |
109 | if (!ilwt->lwt_output) |
110 | ila_update_ipv6_locator(skb, |
111 | p: ila_params_lwtunnel(lwt: dst->lwtstate), |
112 | set_csum_neutral: false); |
113 | |
114 | return dst->lwtstate->orig_input(skb); |
115 | |
116 | drop: |
117 | kfree_skb(skb); |
118 | return -EINVAL; |
119 | } |
120 | |
121 | static const struct nla_policy ila_nl_policy[ILA_ATTR_MAX + 1] = { |
122 | [ILA_ATTR_LOCATOR] = { .type = NLA_U64, }, |
123 | [ILA_ATTR_CSUM_MODE] = { .type = NLA_U8, }, |
124 | [ILA_ATTR_IDENT_TYPE] = { .type = NLA_U8, }, |
125 | [ILA_ATTR_HOOK_TYPE] = { .type = NLA_U8, }, |
126 | }; |
127 | |
128 | static int ila_build_state(struct net *net, struct nlattr *nla, |
129 | unsigned int family, const void *cfg, |
130 | struct lwtunnel_state **ts, |
131 | struct netlink_ext_ack *extack) |
132 | { |
133 | struct ila_lwt *ilwt; |
134 | struct ila_params *p; |
135 | struct nlattr *tb[ILA_ATTR_MAX + 1]; |
136 | struct lwtunnel_state *newts; |
137 | const struct fib6_config *cfg6 = cfg; |
138 | struct ila_addr *iaddr; |
139 | u8 ident_type = ILA_ATYPE_USE_FORMAT; |
140 | u8 hook_type = ILA_HOOK_ROUTE_OUTPUT; |
141 | u8 csum_mode = ILA_CSUM_NO_ACTION; |
142 | bool lwt_output = true; |
143 | u8 eff_ident_type; |
144 | int ret; |
145 | |
146 | if (family != AF_INET6) |
147 | return -EINVAL; |
148 | |
149 | ret = nla_parse_nested_deprecated(tb, ILA_ATTR_MAX, nla, |
150 | policy: ila_nl_policy, extack); |
151 | if (ret < 0) |
152 | return ret; |
153 | |
154 | if (!tb[ILA_ATTR_LOCATOR]) |
155 | return -EINVAL; |
156 | |
157 | iaddr = (struct ila_addr *)&cfg6->fc_dst; |
158 | |
159 | if (tb[ILA_ATTR_IDENT_TYPE]) |
160 | ident_type = nla_get_u8(nla: tb[ILA_ATTR_IDENT_TYPE]); |
161 | |
162 | if (ident_type == ILA_ATYPE_USE_FORMAT) { |
163 | /* Infer identifier type from type field in formatted |
164 | * identifier. |
165 | */ |
166 | |
167 | if (cfg6->fc_dst_len < 8 * sizeof(struct ila_locator) + 3) { |
168 | /* Need to have full locator and at least type field |
169 | * included in destination |
170 | */ |
171 | return -EINVAL; |
172 | } |
173 | |
174 | eff_ident_type = iaddr->ident.type; |
175 | } else { |
176 | eff_ident_type = ident_type; |
177 | } |
178 | |
179 | switch (eff_ident_type) { |
180 | case ILA_ATYPE_IID: |
181 | /* Don't allow ILA for IID type */ |
182 | return -EINVAL; |
183 | case ILA_ATYPE_LUID: |
184 | break; |
185 | case ILA_ATYPE_VIRT_V4: |
186 | case ILA_ATYPE_VIRT_UNI_V6: |
187 | case ILA_ATYPE_VIRT_MULTI_V6: |
188 | case ILA_ATYPE_NONLOCAL_ADDR: |
189 | /* These ILA formats are not supported yet. */ |
190 | default: |
191 | return -EINVAL; |
192 | } |
193 | |
194 | if (tb[ILA_ATTR_HOOK_TYPE]) |
195 | hook_type = nla_get_u8(nla: tb[ILA_ATTR_HOOK_TYPE]); |
196 | |
197 | switch (hook_type) { |
198 | case ILA_HOOK_ROUTE_OUTPUT: |
199 | lwt_output = true; |
200 | break; |
201 | case ILA_HOOK_ROUTE_INPUT: |
202 | lwt_output = false; |
203 | break; |
204 | default: |
205 | return -EINVAL; |
206 | } |
207 | |
208 | if (tb[ILA_ATTR_CSUM_MODE]) |
209 | csum_mode = nla_get_u8(nla: tb[ILA_ATTR_CSUM_MODE]); |
210 | |
211 | if (csum_mode == ILA_CSUM_NEUTRAL_MAP && |
212 | ila_csum_neutral_set(ident: iaddr->ident)) { |
213 | /* Don't allow translation if checksum neutral bit is |
214 | * configured and it's set in the SIR address. |
215 | */ |
216 | return -EINVAL; |
217 | } |
218 | |
219 | newts = lwtunnel_state_alloc(hdr_len: sizeof(*ilwt)); |
220 | if (!newts) |
221 | return -ENOMEM; |
222 | |
223 | ilwt = ila_lwt_lwtunnel(lwt: newts); |
224 | ret = dst_cache_init(dst_cache: &ilwt->dst_cache, GFP_ATOMIC); |
225 | if (ret) { |
226 | kfree(objp: newts); |
227 | return ret; |
228 | } |
229 | |
230 | ilwt->lwt_output = !!lwt_output; |
231 | |
232 | p = ila_params_lwtunnel(lwt: newts); |
233 | |
234 | p->csum_mode = csum_mode; |
235 | p->ident_type = ident_type; |
236 | p->locator.v64 = (__force __be64)nla_get_u64(nla: tb[ILA_ATTR_LOCATOR]); |
237 | |
238 | /* Precompute checksum difference for translation since we |
239 | * know both the old locator and the new one. |
240 | */ |
241 | p->locator_match = iaddr->loc; |
242 | |
243 | ila_init_saved_csum(p); |
244 | |
245 | newts->type = LWTUNNEL_ENCAP_ILA; |
246 | newts->flags |= LWTUNNEL_STATE_OUTPUT_REDIRECT | |
247 | LWTUNNEL_STATE_INPUT_REDIRECT; |
248 | |
249 | if (cfg6->fc_dst_len == 8 * sizeof(struct in6_addr)) |
250 | ilwt->connected = 1; |
251 | |
252 | *ts = newts; |
253 | |
254 | return 0; |
255 | } |
256 | |
257 | static void ila_destroy_state(struct lwtunnel_state *lwt) |
258 | { |
259 | dst_cache_destroy(dst_cache: &ila_lwt_lwtunnel(lwt)->dst_cache); |
260 | } |
261 | |
262 | static int ila_fill_encap_info(struct sk_buff *skb, |
263 | struct lwtunnel_state *lwtstate) |
264 | { |
265 | struct ila_params *p = ila_params_lwtunnel(lwt: lwtstate); |
266 | struct ila_lwt *ilwt = ila_lwt_lwtunnel(lwt: lwtstate); |
267 | |
268 | if (nla_put_u64_64bit(skb, attrtype: ILA_ATTR_LOCATOR, value: (__force u64)p->locator.v64, |
269 | padattr: ILA_ATTR_PAD)) |
270 | goto nla_put_failure; |
271 | |
272 | if (nla_put_u8(skb, attrtype: ILA_ATTR_CSUM_MODE, value: (__force u8)p->csum_mode)) |
273 | goto nla_put_failure; |
274 | |
275 | if (nla_put_u8(skb, attrtype: ILA_ATTR_IDENT_TYPE, value: (__force u8)p->ident_type)) |
276 | goto nla_put_failure; |
277 | |
278 | if (nla_put_u8(skb, attrtype: ILA_ATTR_HOOK_TYPE, |
279 | value: ilwt->lwt_output ? ILA_HOOK_ROUTE_OUTPUT : |
280 | ILA_HOOK_ROUTE_INPUT)) |
281 | goto nla_put_failure; |
282 | |
283 | return 0; |
284 | |
285 | nla_put_failure: |
286 | return -EMSGSIZE; |
287 | } |
288 | |
289 | static int ila_encap_nlsize(struct lwtunnel_state *lwtstate) |
290 | { |
291 | return nla_total_size_64bit(payload: sizeof(u64)) + /* ILA_ATTR_LOCATOR */ |
292 | nla_total_size(payload: sizeof(u8)) + /* ILA_ATTR_CSUM_MODE */ |
293 | nla_total_size(payload: sizeof(u8)) + /* ILA_ATTR_IDENT_TYPE */ |
294 | nla_total_size(payload: sizeof(u8)) + /* ILA_ATTR_HOOK_TYPE */ |
295 | 0; |
296 | } |
297 | |
298 | static int ila_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) |
299 | { |
300 | struct ila_params *a_p = ila_params_lwtunnel(lwt: a); |
301 | struct ila_params *b_p = ila_params_lwtunnel(lwt: b); |
302 | |
303 | return (a_p->locator.v64 != b_p->locator.v64); |
304 | } |
305 | |
306 | static const struct lwtunnel_encap_ops ila_encap_ops = { |
307 | .build_state = ila_build_state, |
308 | .destroy_state = ila_destroy_state, |
309 | .output = ila_output, |
310 | .input = ila_input, |
311 | .fill_encap = ila_fill_encap_info, |
312 | .get_encap_size = ila_encap_nlsize, |
313 | .cmp_encap = ila_encap_cmp, |
314 | .owner = THIS_MODULE, |
315 | }; |
316 | |
317 | int ila_lwt_init(void) |
318 | { |
319 | return lwtunnel_encap_add_ops(op: &ila_encap_ops, num: LWTUNNEL_ENCAP_ILA); |
320 | } |
321 | |
322 | void ila_lwt_fini(void) |
323 | { |
324 | lwtunnel_encap_del_ops(op: &ila_encap_ops, num: LWTUNNEL_ENCAP_ILA); |
325 | } |
326 | |