1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (C) B.A.T.M.A.N. contributors: |
3 | * |
4 | * Marek Lindner |
5 | */ |
6 | |
7 | #include "gateway_client.h" |
8 | #include "main.h" |
9 | |
10 | #include <linux/atomic.h> |
11 | #include <linux/byteorder/generic.h> |
12 | #include <linux/container_of.h> |
13 | #include <linux/errno.h> |
14 | #include <linux/etherdevice.h> |
15 | #include <linux/gfp.h> |
16 | #include <linux/if_ether.h> |
17 | #include <linux/if_vlan.h> |
18 | #include <linux/in.h> |
19 | #include <linux/ip.h> |
20 | #include <linux/ipv6.h> |
21 | #include <linux/kernel.h> |
22 | #include <linux/kref.h> |
23 | #include <linux/list.h> |
24 | #include <linux/lockdep.h> |
25 | #include <linux/netdevice.h> |
26 | #include <linux/netlink.h> |
27 | #include <linux/rculist.h> |
28 | #include <linux/rcupdate.h> |
29 | #include <linux/skbuff.h> |
30 | #include <linux/slab.h> |
31 | #include <linux/spinlock.h> |
32 | #include <linux/stddef.h> |
33 | #include <linux/udp.h> |
34 | #include <net/sock.h> |
35 | #include <uapi/linux/batadv_packet.h> |
36 | #include <uapi/linux/batman_adv.h> |
37 | |
38 | #include "hard-interface.h" |
39 | #include "log.h" |
40 | #include "netlink.h" |
41 | #include "originator.h" |
42 | #include "routing.h" |
43 | #include "soft-interface.h" |
44 | #include "translation-table.h" |
45 | |
46 | /* These are the offsets of the "hw type" and "hw address length" in the dhcp |
47 | * packet starting at the beginning of the dhcp header |
48 | */ |
49 | #define BATADV_DHCP_HTYPE_OFFSET 1 |
50 | #define BATADV_DHCP_HLEN_OFFSET 2 |
51 | /* Value of htype representing Ethernet */ |
52 | #define BATADV_DHCP_HTYPE_ETHERNET 0x01 |
53 | /* This is the offset of the "chaddr" field in the dhcp packet starting at the |
54 | * beginning of the dhcp header |
55 | */ |
56 | #define BATADV_DHCP_CHADDR_OFFSET 28 |
57 | |
58 | /** |
59 | * batadv_gw_node_release() - release gw_node from lists and queue for free |
60 | * after rcu grace period |
61 | * @ref: kref pointer of the gw_node |
62 | */ |
63 | void batadv_gw_node_release(struct kref *ref) |
64 | { |
65 | struct batadv_gw_node *gw_node; |
66 | |
67 | gw_node = container_of(ref, struct batadv_gw_node, refcount); |
68 | |
69 | batadv_orig_node_put(orig_node: gw_node->orig_node); |
70 | kfree_rcu(gw_node, rcu); |
71 | } |
72 | |
73 | /** |
74 | * batadv_gw_get_selected_gw_node() - Get currently selected gateway |
75 | * @bat_priv: the bat priv with all the soft interface information |
76 | * |
77 | * Return: selected gateway (with increased refcnt), NULL on errors |
78 | */ |
79 | struct batadv_gw_node * |
80 | batadv_gw_get_selected_gw_node(struct batadv_priv *bat_priv) |
81 | { |
82 | struct batadv_gw_node *gw_node; |
83 | |
84 | rcu_read_lock(); |
85 | gw_node = rcu_dereference(bat_priv->gw.curr_gw); |
86 | if (!gw_node) |
87 | goto out; |
88 | |
89 | if (!kref_get_unless_zero(kref: &gw_node->refcount)) |
90 | gw_node = NULL; |
91 | |
92 | out: |
93 | rcu_read_unlock(); |
94 | return gw_node; |
95 | } |
96 | |
97 | /** |
98 | * batadv_gw_get_selected_orig() - Get originator of currently selected gateway |
99 | * @bat_priv: the bat priv with all the soft interface information |
100 | * |
101 | * Return: orig_node of selected gateway (with increased refcnt), NULL on errors |
102 | */ |
103 | struct batadv_orig_node * |
104 | batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) |
105 | { |
106 | struct batadv_gw_node *gw_node; |
107 | struct batadv_orig_node *orig_node = NULL; |
108 | |
109 | gw_node = batadv_gw_get_selected_gw_node(bat_priv); |
110 | if (!gw_node) |
111 | goto out; |
112 | |
113 | rcu_read_lock(); |
114 | orig_node = gw_node->orig_node; |
115 | if (!orig_node) |
116 | goto unlock; |
117 | |
118 | if (!kref_get_unless_zero(kref: &orig_node->refcount)) |
119 | orig_node = NULL; |
120 | |
121 | unlock: |
122 | rcu_read_unlock(); |
123 | out: |
124 | batadv_gw_node_put(gw_node); |
125 | return orig_node; |
126 | } |
127 | |
128 | static void batadv_gw_select(struct batadv_priv *bat_priv, |
129 | struct batadv_gw_node *new_gw_node) |
130 | { |
131 | struct batadv_gw_node *curr_gw_node; |
132 | |
133 | spin_lock_bh(lock: &bat_priv->gw.list_lock); |
134 | |
135 | if (new_gw_node) |
136 | kref_get(kref: &new_gw_node->refcount); |
137 | |
138 | curr_gw_node = rcu_replace_pointer(bat_priv->gw.curr_gw, new_gw_node, |
139 | true); |
140 | |
141 | batadv_gw_node_put(gw_node: curr_gw_node); |
142 | |
143 | spin_unlock_bh(lock: &bat_priv->gw.list_lock); |
144 | } |
145 | |
146 | /** |
147 | * batadv_gw_reselect() - force a gateway reselection |
148 | * @bat_priv: the bat priv with all the soft interface information |
149 | * |
150 | * Set a flag to remind the GW component to perform a new gateway reselection. |
151 | * However this function does not ensure that the current gateway is going to be |
152 | * deselected. The reselection mechanism may elect the same gateway once again. |
153 | * |
154 | * This means that invoking batadv_gw_reselect() does not guarantee a gateway |
155 | * change and therefore a uevent is not necessarily expected. |
156 | */ |
157 | void batadv_gw_reselect(struct batadv_priv *bat_priv) |
158 | { |
159 | atomic_set(v: &bat_priv->gw.reselect, i: 1); |
160 | } |
161 | |
162 | /** |
163 | * batadv_gw_check_client_stop() - check if client mode has been switched off |
164 | * @bat_priv: the bat priv with all the soft interface information |
165 | * |
166 | * This function assumes the caller has checked that the gw state *is actually |
167 | * changing*. This function is not supposed to be called when there is no state |
168 | * change. |
169 | */ |
170 | void batadv_gw_check_client_stop(struct batadv_priv *bat_priv) |
171 | { |
172 | struct batadv_gw_node *curr_gw; |
173 | |
174 | if (atomic_read(v: &bat_priv->gw.mode) != BATADV_GW_MODE_CLIENT) |
175 | return; |
176 | |
177 | curr_gw = batadv_gw_get_selected_gw_node(bat_priv); |
178 | if (!curr_gw) |
179 | return; |
180 | |
181 | /* deselect the current gateway so that next time that client mode is |
182 | * enabled a proper GW_ADD event can be sent |
183 | */ |
184 | batadv_gw_select(bat_priv, NULL); |
185 | |
186 | /* if batman-adv is switching the gw client mode off and a gateway was |
187 | * already selected, send a DEL uevent |
188 | */ |
189 | batadv_throw_uevent(bat_priv, type: BATADV_UEV_GW, action: BATADV_UEV_DEL, NULL); |
190 | |
191 | batadv_gw_node_put(gw_node: curr_gw); |
192 | } |
193 | |
194 | /** |
195 | * batadv_gw_election() - Elect the best gateway |
196 | * @bat_priv: the bat priv with all the soft interface information |
197 | */ |
198 | void batadv_gw_election(struct batadv_priv *bat_priv) |
199 | { |
200 | struct batadv_gw_node *curr_gw = NULL; |
201 | struct batadv_gw_node *next_gw = NULL; |
202 | struct batadv_neigh_node *router = NULL; |
203 | struct batadv_neigh_ifinfo *router_ifinfo = NULL; |
204 | char gw_addr[18] = { '\0' }; |
205 | |
206 | if (atomic_read(v: &bat_priv->gw.mode) != BATADV_GW_MODE_CLIENT) |
207 | goto out; |
208 | |
209 | if (!bat_priv->algo_ops->gw.get_best_gw_node) |
210 | goto out; |
211 | |
212 | curr_gw = batadv_gw_get_selected_gw_node(bat_priv); |
213 | |
214 | if (!batadv_atomic_dec_not_zero(&bat_priv->gw.reselect) && curr_gw) |
215 | goto out; |
216 | |
217 | /* if gw.reselect is set to 1 it means that a previous call to |
218 | * gw.is_eligible() said that we have a new best GW, therefore it can |
219 | * now be picked from the list and selected |
220 | */ |
221 | next_gw = bat_priv->algo_ops->gw.get_best_gw_node(bat_priv); |
222 | |
223 | if (curr_gw == next_gw) |
224 | goto out; |
225 | |
226 | if (next_gw) { |
227 | sprintf(buf: gw_addr, fmt: "%pM" , next_gw->orig_node->orig); |
228 | |
229 | router = batadv_orig_router_get(orig_node: next_gw->orig_node, |
230 | BATADV_IF_DEFAULT); |
231 | if (!router) { |
232 | batadv_gw_reselect(bat_priv); |
233 | goto out; |
234 | } |
235 | |
236 | router_ifinfo = batadv_neigh_ifinfo_get(neigh: router, |
237 | BATADV_IF_DEFAULT); |
238 | if (!router_ifinfo) { |
239 | batadv_gw_reselect(bat_priv); |
240 | goto out; |
241 | } |
242 | } |
243 | |
244 | if (curr_gw && !next_gw) { |
245 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
246 | "Removing selected gateway - no gateway in range\n" ); |
247 | batadv_throw_uevent(bat_priv, type: BATADV_UEV_GW, action: BATADV_UEV_DEL, |
248 | NULL); |
249 | } else if (!curr_gw && next_gw) { |
250 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
251 | "Adding route to gateway %pM (bandwidth: %u.%u/%u.%u MBit, tq: %i)\n" , |
252 | next_gw->orig_node->orig, |
253 | next_gw->bandwidth_down / 10, |
254 | next_gw->bandwidth_down % 10, |
255 | next_gw->bandwidth_up / 10, |
256 | next_gw->bandwidth_up % 10, |
257 | router_ifinfo->bat_iv.tq_avg); |
258 | batadv_throw_uevent(bat_priv, type: BATADV_UEV_GW, action: BATADV_UEV_ADD, |
259 | data: gw_addr); |
260 | } else { |
261 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
262 | "Changing route to gateway %pM (bandwidth: %u.%u/%u.%u MBit, tq: %i)\n" , |
263 | next_gw->orig_node->orig, |
264 | next_gw->bandwidth_down / 10, |
265 | next_gw->bandwidth_down % 10, |
266 | next_gw->bandwidth_up / 10, |
267 | next_gw->bandwidth_up % 10, |
268 | router_ifinfo->bat_iv.tq_avg); |
269 | batadv_throw_uevent(bat_priv, type: BATADV_UEV_GW, action: BATADV_UEV_CHANGE, |
270 | data: gw_addr); |
271 | } |
272 | |
273 | batadv_gw_select(bat_priv, new_gw_node: next_gw); |
274 | |
275 | out: |
276 | batadv_gw_node_put(gw_node: curr_gw); |
277 | batadv_gw_node_put(gw_node: next_gw); |
278 | batadv_neigh_node_put(neigh_node: router); |
279 | batadv_neigh_ifinfo_put(neigh_ifinfo: router_ifinfo); |
280 | } |
281 | |
282 | /** |
283 | * batadv_gw_check_election() - Elect orig node as best gateway when eligible |
284 | * @bat_priv: the bat priv with all the soft interface information |
285 | * @orig_node: orig node which is to be checked |
286 | */ |
287 | void batadv_gw_check_election(struct batadv_priv *bat_priv, |
288 | struct batadv_orig_node *orig_node) |
289 | { |
290 | struct batadv_orig_node *curr_gw_orig; |
291 | |
292 | /* abort immediately if the routing algorithm does not support gateway |
293 | * election |
294 | */ |
295 | if (!bat_priv->algo_ops->gw.is_eligible) |
296 | return; |
297 | |
298 | curr_gw_orig = batadv_gw_get_selected_orig(bat_priv); |
299 | if (!curr_gw_orig) |
300 | goto reselect; |
301 | |
302 | /* this node already is the gateway */ |
303 | if (curr_gw_orig == orig_node) |
304 | goto out; |
305 | |
306 | if (!bat_priv->algo_ops->gw.is_eligible(bat_priv, curr_gw_orig, |
307 | orig_node)) |
308 | goto out; |
309 | |
310 | reselect: |
311 | batadv_gw_reselect(bat_priv); |
312 | out: |
313 | batadv_orig_node_put(orig_node: curr_gw_orig); |
314 | } |
315 | |
316 | /** |
317 | * batadv_gw_node_add() - add gateway node to list of available gateways |
318 | * @bat_priv: the bat priv with all the soft interface information |
319 | * @orig_node: originator announcing gateway capabilities |
320 | * @gateway: announced bandwidth information |
321 | * |
322 | * Has to be called with the appropriate locks being acquired |
323 | * (gw.list_lock). |
324 | */ |
325 | static void batadv_gw_node_add(struct batadv_priv *bat_priv, |
326 | struct batadv_orig_node *orig_node, |
327 | struct batadv_tvlv_gateway_data *gateway) |
328 | { |
329 | struct batadv_gw_node *gw_node; |
330 | |
331 | lockdep_assert_held(&bat_priv->gw.list_lock); |
332 | |
333 | if (gateway->bandwidth_down == 0) |
334 | return; |
335 | |
336 | gw_node = kzalloc(size: sizeof(*gw_node), GFP_ATOMIC); |
337 | if (!gw_node) |
338 | return; |
339 | |
340 | kref_init(kref: &gw_node->refcount); |
341 | INIT_HLIST_NODE(h: &gw_node->list); |
342 | kref_get(kref: &orig_node->refcount); |
343 | gw_node->orig_node = orig_node; |
344 | gw_node->bandwidth_down = ntohl(gateway->bandwidth_down); |
345 | gw_node->bandwidth_up = ntohl(gateway->bandwidth_up); |
346 | |
347 | kref_get(kref: &gw_node->refcount); |
348 | hlist_add_head_rcu(n: &gw_node->list, h: &bat_priv->gw.gateway_list); |
349 | bat_priv->gw.generation++; |
350 | |
351 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
352 | "Found new gateway %pM -> gw bandwidth: %u.%u/%u.%u MBit\n" , |
353 | orig_node->orig, |
354 | ntohl(gateway->bandwidth_down) / 10, |
355 | ntohl(gateway->bandwidth_down) % 10, |
356 | ntohl(gateway->bandwidth_up) / 10, |
357 | ntohl(gateway->bandwidth_up) % 10); |
358 | |
359 | /* don't return reference to new gw_node */ |
360 | batadv_gw_node_put(gw_node); |
361 | } |
362 | |
363 | /** |
364 | * batadv_gw_node_get() - retrieve gateway node from list of available gateways |
365 | * @bat_priv: the bat priv with all the soft interface information |
366 | * @orig_node: originator announcing gateway capabilities |
367 | * |
368 | * Return: gateway node if found or NULL otherwise. |
369 | */ |
370 | struct batadv_gw_node *batadv_gw_node_get(struct batadv_priv *bat_priv, |
371 | struct batadv_orig_node *orig_node) |
372 | { |
373 | struct batadv_gw_node *gw_node_tmp, *gw_node = NULL; |
374 | |
375 | rcu_read_lock(); |
376 | hlist_for_each_entry_rcu(gw_node_tmp, &bat_priv->gw.gateway_list, |
377 | list) { |
378 | if (gw_node_tmp->orig_node != orig_node) |
379 | continue; |
380 | |
381 | if (!kref_get_unless_zero(kref: &gw_node_tmp->refcount)) |
382 | continue; |
383 | |
384 | gw_node = gw_node_tmp; |
385 | break; |
386 | } |
387 | rcu_read_unlock(); |
388 | |
389 | return gw_node; |
390 | } |
391 | |
392 | /** |
393 | * batadv_gw_node_update() - update list of available gateways with changed |
394 | * bandwidth information |
395 | * @bat_priv: the bat priv with all the soft interface information |
396 | * @orig_node: originator announcing gateway capabilities |
397 | * @gateway: announced bandwidth information |
398 | */ |
399 | void batadv_gw_node_update(struct batadv_priv *bat_priv, |
400 | struct batadv_orig_node *orig_node, |
401 | struct batadv_tvlv_gateway_data *gateway) |
402 | { |
403 | struct batadv_gw_node *gw_node, *curr_gw = NULL; |
404 | |
405 | spin_lock_bh(lock: &bat_priv->gw.list_lock); |
406 | gw_node = batadv_gw_node_get(bat_priv, orig_node); |
407 | if (!gw_node) { |
408 | batadv_gw_node_add(bat_priv, orig_node, gateway); |
409 | spin_unlock_bh(lock: &bat_priv->gw.list_lock); |
410 | goto out; |
411 | } |
412 | spin_unlock_bh(lock: &bat_priv->gw.list_lock); |
413 | |
414 | if (gw_node->bandwidth_down == ntohl(gateway->bandwidth_down) && |
415 | gw_node->bandwidth_up == ntohl(gateway->bandwidth_up)) |
416 | goto out; |
417 | |
418 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
419 | "Gateway bandwidth of originator %pM changed from %u.%u/%u.%u MBit to %u.%u/%u.%u MBit\n" , |
420 | orig_node->orig, |
421 | gw_node->bandwidth_down / 10, |
422 | gw_node->bandwidth_down % 10, |
423 | gw_node->bandwidth_up / 10, |
424 | gw_node->bandwidth_up % 10, |
425 | ntohl(gateway->bandwidth_down) / 10, |
426 | ntohl(gateway->bandwidth_down) % 10, |
427 | ntohl(gateway->bandwidth_up) / 10, |
428 | ntohl(gateway->bandwidth_up) % 10); |
429 | |
430 | gw_node->bandwidth_down = ntohl(gateway->bandwidth_down); |
431 | gw_node->bandwidth_up = ntohl(gateway->bandwidth_up); |
432 | |
433 | if (ntohl(gateway->bandwidth_down) == 0) { |
434 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
435 | "Gateway %pM removed from gateway list\n" , |
436 | orig_node->orig); |
437 | |
438 | /* Note: We don't need a NULL check here, since curr_gw never |
439 | * gets dereferenced. |
440 | */ |
441 | spin_lock_bh(lock: &bat_priv->gw.list_lock); |
442 | if (!hlist_unhashed(h: &gw_node->list)) { |
443 | hlist_del_init_rcu(n: &gw_node->list); |
444 | batadv_gw_node_put(gw_node); |
445 | bat_priv->gw.generation++; |
446 | } |
447 | spin_unlock_bh(lock: &bat_priv->gw.list_lock); |
448 | |
449 | curr_gw = batadv_gw_get_selected_gw_node(bat_priv); |
450 | if (gw_node == curr_gw) |
451 | batadv_gw_reselect(bat_priv); |
452 | |
453 | batadv_gw_node_put(gw_node: curr_gw); |
454 | } |
455 | |
456 | out: |
457 | batadv_gw_node_put(gw_node); |
458 | } |
459 | |
460 | /** |
461 | * batadv_gw_node_delete() - Remove orig_node from gateway list |
462 | * @bat_priv: the bat priv with all the soft interface information |
463 | * @orig_node: orig node which is currently in process of being removed |
464 | */ |
465 | void batadv_gw_node_delete(struct batadv_priv *bat_priv, |
466 | struct batadv_orig_node *orig_node) |
467 | { |
468 | struct batadv_tvlv_gateway_data gateway; |
469 | |
470 | gateway.bandwidth_down = 0; |
471 | gateway.bandwidth_up = 0; |
472 | |
473 | batadv_gw_node_update(bat_priv, orig_node, gateway: &gateway); |
474 | } |
475 | |
476 | /** |
477 | * batadv_gw_node_free() - Free gateway information from soft interface |
478 | * @bat_priv: the bat priv with all the soft interface information |
479 | */ |
480 | void batadv_gw_node_free(struct batadv_priv *bat_priv) |
481 | { |
482 | struct batadv_gw_node *gw_node; |
483 | struct hlist_node *node_tmp; |
484 | |
485 | spin_lock_bh(lock: &bat_priv->gw.list_lock); |
486 | hlist_for_each_entry_safe(gw_node, node_tmp, |
487 | &bat_priv->gw.gateway_list, list) { |
488 | hlist_del_init_rcu(n: &gw_node->list); |
489 | batadv_gw_node_put(gw_node); |
490 | bat_priv->gw.generation++; |
491 | } |
492 | spin_unlock_bh(lock: &bat_priv->gw.list_lock); |
493 | } |
494 | |
495 | /** |
496 | * batadv_gw_dump() - Dump gateways into a message |
497 | * @msg: Netlink message to dump into |
498 | * @cb: Control block containing additional options |
499 | * |
500 | * Return: Error code, or length of message |
501 | */ |
502 | int batadv_gw_dump(struct sk_buff *msg, struct netlink_callback *cb) |
503 | { |
504 | struct batadv_hard_iface *primary_if = NULL; |
505 | struct net *net = sock_net(sk: cb->skb->sk); |
506 | struct net_device *soft_iface; |
507 | struct batadv_priv *bat_priv; |
508 | int ifindex; |
509 | int ret; |
510 | |
511 | ifindex = batadv_netlink_get_ifindex(nlh: cb->nlh, |
512 | attrtype: BATADV_ATTR_MESH_IFINDEX); |
513 | if (!ifindex) |
514 | return -EINVAL; |
515 | |
516 | soft_iface = dev_get_by_index(net, ifindex); |
517 | if (!soft_iface || !batadv_softif_is_valid(net_dev: soft_iface)) { |
518 | ret = -ENODEV; |
519 | goto out; |
520 | } |
521 | |
522 | bat_priv = netdev_priv(dev: soft_iface); |
523 | |
524 | primary_if = batadv_primary_if_get_selected(bat_priv); |
525 | if (!primary_if || primary_if->if_status != BATADV_IF_ACTIVE) { |
526 | ret = -ENOENT; |
527 | goto out; |
528 | } |
529 | |
530 | if (!bat_priv->algo_ops->gw.dump) { |
531 | ret = -EOPNOTSUPP; |
532 | goto out; |
533 | } |
534 | |
535 | bat_priv->algo_ops->gw.dump(msg, cb, bat_priv); |
536 | |
537 | ret = msg->len; |
538 | |
539 | out: |
540 | batadv_hardif_put(hard_iface: primary_if); |
541 | dev_put(dev: soft_iface); |
542 | |
543 | return ret; |
544 | } |
545 | |
546 | /** |
547 | * batadv_gw_dhcp_recipient_get() - check if a packet is a DHCP message |
548 | * @skb: the packet to check |
549 | * @header_len: a pointer to the batman-adv header size |
550 | * @chaddr: buffer where the client address will be stored. Valid |
551 | * only if the function returns BATADV_DHCP_TO_CLIENT |
552 | * |
553 | * This function may re-allocate the data buffer of the skb passed as argument. |
554 | * |
555 | * Return: |
556 | * - BATADV_DHCP_NO if the packet is not a dhcp message or if there was an error |
557 | * while parsing it |
558 | * - BATADV_DHCP_TO_SERVER if this is a message going to the DHCP server |
559 | * - BATADV_DHCP_TO_CLIENT if this is a message going to a DHCP client |
560 | */ |
561 | enum batadv_dhcp_recipient |
562 | batadv_gw_dhcp_recipient_get(struct sk_buff *skb, unsigned int *, |
563 | u8 *chaddr) |
564 | { |
565 | enum batadv_dhcp_recipient ret = BATADV_DHCP_NO; |
566 | struct ethhdr *ethhdr; |
567 | struct iphdr *iphdr; |
568 | struct ipv6hdr *ipv6hdr; |
569 | struct udphdr *udphdr; |
570 | struct vlan_ethhdr *vhdr; |
571 | int chaddr_offset; |
572 | __be16 proto; |
573 | u8 *p; |
574 | |
575 | /* check for ethernet header */ |
576 | if (!pskb_may_pull(skb, len: *header_len + ETH_HLEN)) |
577 | return BATADV_DHCP_NO; |
578 | |
579 | ethhdr = eth_hdr(skb); |
580 | proto = ethhdr->h_proto; |
581 | *header_len += ETH_HLEN; |
582 | |
583 | /* check for initial vlan header */ |
584 | if (proto == htons(ETH_P_8021Q)) { |
585 | if (!pskb_may_pull(skb, len: *header_len + VLAN_HLEN)) |
586 | return BATADV_DHCP_NO; |
587 | |
588 | vhdr = vlan_eth_hdr(skb); |
589 | proto = vhdr->h_vlan_encapsulated_proto; |
590 | *header_len += VLAN_HLEN; |
591 | } |
592 | |
593 | /* check for ip header */ |
594 | switch (proto) { |
595 | case htons(ETH_P_IP): |
596 | if (!pskb_may_pull(skb, len: *header_len + sizeof(*iphdr))) |
597 | return BATADV_DHCP_NO; |
598 | |
599 | iphdr = (struct iphdr *)(skb->data + *header_len); |
600 | *header_len += iphdr->ihl * 4; |
601 | |
602 | /* check for udp header */ |
603 | if (iphdr->protocol != IPPROTO_UDP) |
604 | return BATADV_DHCP_NO; |
605 | |
606 | break; |
607 | case htons(ETH_P_IPV6): |
608 | if (!pskb_may_pull(skb, len: *header_len + sizeof(*ipv6hdr))) |
609 | return BATADV_DHCP_NO; |
610 | |
611 | ipv6hdr = (struct ipv6hdr *)(skb->data + *header_len); |
612 | *header_len += sizeof(*ipv6hdr); |
613 | |
614 | /* check for udp header */ |
615 | if (ipv6hdr->nexthdr != IPPROTO_UDP) |
616 | return BATADV_DHCP_NO; |
617 | |
618 | break; |
619 | default: |
620 | return BATADV_DHCP_NO; |
621 | } |
622 | |
623 | if (!pskb_may_pull(skb, len: *header_len + sizeof(*udphdr))) |
624 | return BATADV_DHCP_NO; |
625 | |
626 | udphdr = (struct udphdr *)(skb->data + *header_len); |
627 | *header_len += sizeof(*udphdr); |
628 | |
629 | /* check for bootp port */ |
630 | switch (proto) { |
631 | case htons(ETH_P_IP): |
632 | if (udphdr->dest == htons(67)) |
633 | ret = BATADV_DHCP_TO_SERVER; |
634 | else if (udphdr->source == htons(67)) |
635 | ret = BATADV_DHCP_TO_CLIENT; |
636 | break; |
637 | case htons(ETH_P_IPV6): |
638 | if (udphdr->dest == htons(547)) |
639 | ret = BATADV_DHCP_TO_SERVER; |
640 | else if (udphdr->source == htons(547)) |
641 | ret = BATADV_DHCP_TO_CLIENT; |
642 | break; |
643 | } |
644 | |
645 | chaddr_offset = *header_len + BATADV_DHCP_CHADDR_OFFSET; |
646 | /* store the client address if the message is going to a client */ |
647 | if (ret == BATADV_DHCP_TO_CLIENT) { |
648 | if (!pskb_may_pull(skb, len: chaddr_offset + ETH_ALEN)) |
649 | return BATADV_DHCP_NO; |
650 | |
651 | /* check if the DHCP packet carries an Ethernet DHCP */ |
652 | p = skb->data + *header_len + BATADV_DHCP_HTYPE_OFFSET; |
653 | if (*p != BATADV_DHCP_HTYPE_ETHERNET) |
654 | return BATADV_DHCP_NO; |
655 | |
656 | /* check if the DHCP packet carries a valid Ethernet address */ |
657 | p = skb->data + *header_len + BATADV_DHCP_HLEN_OFFSET; |
658 | if (*p != ETH_ALEN) |
659 | return BATADV_DHCP_NO; |
660 | |
661 | ether_addr_copy(dst: chaddr, src: skb->data + chaddr_offset); |
662 | } |
663 | |
664 | return ret; |
665 | } |
666 | |
667 | /** |
668 | * batadv_gw_out_of_range() - check if the dhcp request destination is the best |
669 | * gateway |
670 | * @bat_priv: the bat priv with all the soft interface information |
671 | * @skb: the outgoing packet |
672 | * |
673 | * Check if the skb is a DHCP request and if it is sent to the current best GW |
674 | * server. Due to topology changes it may be the case that the GW server |
675 | * previously selected is not the best one anymore. |
676 | * |
677 | * This call might reallocate skb data. |
678 | * Must be invoked only when the DHCP packet is going TO a DHCP SERVER. |
679 | * |
680 | * Return: true if the packet destination is unicast and it is not the best gw, |
681 | * false otherwise. |
682 | */ |
683 | bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, |
684 | struct sk_buff *skb) |
685 | { |
686 | struct batadv_neigh_node *neigh_curr = NULL; |
687 | struct batadv_neigh_node *neigh_old = NULL; |
688 | struct batadv_orig_node *orig_dst_node = NULL; |
689 | struct batadv_gw_node *gw_node = NULL; |
690 | struct batadv_gw_node *curr_gw = NULL; |
691 | struct batadv_neigh_ifinfo *curr_ifinfo, *old_ifinfo; |
692 | struct ethhdr *ethhdr = (struct ethhdr *)skb->data; |
693 | bool out_of_range = false; |
694 | u8 curr_tq_avg; |
695 | unsigned short vid; |
696 | |
697 | vid = batadv_get_vid(skb, header_len: 0); |
698 | |
699 | if (is_multicast_ether_addr(addr: ethhdr->h_dest)) |
700 | goto out; |
701 | |
702 | orig_dst_node = batadv_transtable_search(bat_priv, src: ethhdr->h_source, |
703 | addr: ethhdr->h_dest, vid); |
704 | if (!orig_dst_node) |
705 | goto out; |
706 | |
707 | gw_node = batadv_gw_node_get(bat_priv, orig_node: orig_dst_node); |
708 | if (!gw_node) |
709 | goto out; |
710 | |
711 | switch (atomic_read(v: &bat_priv->gw.mode)) { |
712 | case BATADV_GW_MODE_SERVER: |
713 | /* If we are a GW then we are our best GW. We can artificially |
714 | * set the tq towards ourself as the maximum value |
715 | */ |
716 | curr_tq_avg = BATADV_TQ_MAX_VALUE; |
717 | break; |
718 | case BATADV_GW_MODE_CLIENT: |
719 | curr_gw = batadv_gw_get_selected_gw_node(bat_priv); |
720 | if (!curr_gw) |
721 | goto out; |
722 | |
723 | /* packet is going to our gateway */ |
724 | if (curr_gw->orig_node == orig_dst_node) |
725 | goto out; |
726 | |
727 | /* If the dhcp packet has been sent to a different gw, |
728 | * we have to evaluate whether the old gw is still |
729 | * reliable enough |
730 | */ |
731 | neigh_curr = batadv_find_router(bat_priv, orig_node: curr_gw->orig_node, |
732 | NULL); |
733 | if (!neigh_curr) |
734 | goto out; |
735 | |
736 | curr_ifinfo = batadv_neigh_ifinfo_get(neigh: neigh_curr, |
737 | BATADV_IF_DEFAULT); |
738 | if (!curr_ifinfo) |
739 | goto out; |
740 | |
741 | curr_tq_avg = curr_ifinfo->bat_iv.tq_avg; |
742 | batadv_neigh_ifinfo_put(neigh_ifinfo: curr_ifinfo); |
743 | |
744 | break; |
745 | case BATADV_GW_MODE_OFF: |
746 | default: |
747 | goto out; |
748 | } |
749 | |
750 | neigh_old = batadv_find_router(bat_priv, orig_node: orig_dst_node, NULL); |
751 | if (!neigh_old) |
752 | goto out; |
753 | |
754 | old_ifinfo = batadv_neigh_ifinfo_get(neigh: neigh_old, BATADV_IF_DEFAULT); |
755 | if (!old_ifinfo) |
756 | goto out; |
757 | |
758 | if ((curr_tq_avg - old_ifinfo->bat_iv.tq_avg) > BATADV_GW_THRESHOLD) |
759 | out_of_range = true; |
760 | batadv_neigh_ifinfo_put(neigh_ifinfo: old_ifinfo); |
761 | |
762 | out: |
763 | batadv_orig_node_put(orig_node: orig_dst_node); |
764 | batadv_gw_node_put(gw_node: curr_gw); |
765 | batadv_gw_node_put(gw_node); |
766 | batadv_neigh_node_put(neigh_node: neigh_old); |
767 | batadv_neigh_node_put(neigh_node: neigh_curr); |
768 | return out_of_range; |
769 | } |
770 | |