Commit | Line | Data |
---|---|---|
7db7d9f3 | 1 | // SPDX-License-Identifier: GPL-2.0 |
68e039f9 | 2 | /* Copyright (C) 2007-2020 B.A.T.M.A.N. contributors: |
c6c8fea2 SE |
3 | * |
4 | * Marek Lindner | |
c6c8fea2 SE |
5 | */ |
6 | ||
1e2c2a4f | 7 | #include "icmp_socket.h" |
c6c8fea2 | 8 | #include "main.h" |
1e2c2a4f SE |
9 | |
10 | #include <linux/atomic.h> | |
11 | #include <linux/compiler.h> | |
c6c8fea2 | 12 | #include <linux/debugfs.h> |
1e2c2a4f SE |
13 | #include <linux/errno.h> |
14 | #include <linux/etherdevice.h> | |
48881ed5 | 15 | #include <linux/eventpoll.h> |
1e2c2a4f SE |
16 | #include <linux/export.h> |
17 | #include <linux/fcntl.h> | |
18 | #include <linux/fs.h> | |
b92b94ac | 19 | #include <linux/gfp.h> |
1e2c2a4f SE |
20 | #include <linux/if_ether.h> |
21 | #include <linux/kernel.h> | |
22 | #include <linux/list.h> | |
23 | #include <linux/module.h> | |
24 | #include <linux/netdevice.h> | |
25 | #include <linux/pkt_sched.h> | |
26 | #include <linux/poll.h> | |
27 | #include <linux/printk.h> | |
28 | #include <linux/sched.h> /* for linux/wait.h */ | |
29 | #include <linux/skbuff.h> | |
c6c8fea2 | 30 | #include <linux/slab.h> |
1e2c2a4f | 31 | #include <linux/spinlock.h> |
1e2c2a4f SE |
32 | #include <linux/stddef.h> |
33 | #include <linux/string.h> | |
34 | #include <linux/uaccess.h> | |
35 | #include <linux/wait.h> | |
fec149f5 | 36 | #include <uapi/linux/batadv_packet.h> |
1e2c2a4f | 37 | |
00caf6a2 | 38 | #include "debugfs.h" |
c6c8fea2 | 39 | #include "hard-interface.h" |
ba412080 | 40 | #include "log.h" |
1e2c2a4f | 41 | #include "originator.h" |
1e2c2a4f | 42 | #include "send.h" |
c6c8fea2 | 43 | |
56303d34 | 44 | static struct batadv_socket_client *batadv_socket_client_hash[256]; |
c6c8fea2 | 45 | |
56303d34 | 46 | static void batadv_socket_add_packet(struct batadv_socket_client *socket_client, |
da6b8c20 | 47 | struct batadv_icmp_header *icmph, |
af4447f6 | 48 | size_t icmp_len); |
c6c8fea2 | 49 | |
ff15c27c SE |
50 | /** |
51 | * batadv_socket_init() - Initialize soft interface independent socket data | |
52 | */ | |
9039dc7e | 53 | void batadv_socket_init(void) |
c6c8fea2 | 54 | { |
af4447f6 | 55 | memset(batadv_socket_client_hash, 0, sizeof(batadv_socket_client_hash)); |
c6c8fea2 SE |
56 | } |
57 | ||
af4447f6 | 58 | static int batadv_socket_open(struct inode *inode, struct file *file) |
c6c8fea2 SE |
59 | { |
60 | unsigned int i; | |
56303d34 | 61 | struct batadv_socket_client *socket_client; |
c6c8fea2 | 62 | |
bd5b80d5 SE |
63 | if (!try_module_get(THIS_MODULE)) |
64 | return -EBUSY; | |
65 | ||
00caf6a2 SE |
66 | batadv_debugfs_deprecated(file, ""); |
67 | ||
c5bf68fe | 68 | stream_open(inode, file); |
c6c8fea2 | 69 | |
704509b8 | 70 | socket_client = kmalloc(sizeof(*socket_client), GFP_KERNEL); |
bd5b80d5 SE |
71 | if (!socket_client) { |
72 | module_put(THIS_MODULE); | |
c6c8fea2 | 73 | return -ENOMEM; |
bd5b80d5 | 74 | } |
c6c8fea2 | 75 | |
af4447f6 SE |
76 | for (i = 0; i < ARRAY_SIZE(batadv_socket_client_hash); i++) { |
77 | if (!batadv_socket_client_hash[i]) { | |
78 | batadv_socket_client_hash[i] = socket_client; | |
c6c8fea2 SE |
79 | break; |
80 | } | |
81 | } | |
82 | ||
af4447f6 | 83 | if (i == ARRAY_SIZE(batadv_socket_client_hash)) { |
86ceb360 | 84 | pr_err("Error - can't add another packet client: maximum number of clients reached\n"); |
c6c8fea2 | 85 | kfree(socket_client); |
bd5b80d5 | 86 | module_put(THIS_MODULE); |
c6c8fea2 SE |
87 | return -EXFULL; |
88 | } | |
89 | ||
90 | INIT_LIST_HEAD(&socket_client->queue_list); | |
91 | socket_client->queue_len = 0; | |
92 | socket_client->index = i; | |
93 | socket_client->bat_priv = inode->i_private; | |
94 | spin_lock_init(&socket_client->lock); | |
95 | init_waitqueue_head(&socket_client->queue_wait); | |
96 | ||
97 | file->private_data = socket_client; | |
98 | ||
c6c8fea2 SE |
99 | return 0; |
100 | } | |
101 | ||
af4447f6 | 102 | static int batadv_socket_release(struct inode *inode, struct file *file) |
c6c8fea2 | 103 | { |
fb1f23ea GT |
104 | struct batadv_socket_client *client = file->private_data; |
105 | struct batadv_socket_packet *packet, *tmp; | |
c6c8fea2 | 106 | |
fb1f23ea | 107 | spin_lock_bh(&client->lock); |
c6c8fea2 SE |
108 | |
109 | /* for all packets in the queue ... */ | |
fb1f23ea GT |
110 | list_for_each_entry_safe(packet, tmp, &client->queue_list, list) { |
111 | list_del(&packet->list); | |
112 | kfree(packet); | |
c6c8fea2 SE |
113 | } |
114 | ||
fb1f23ea GT |
115 | batadv_socket_client_hash[client->index] = NULL; |
116 | spin_unlock_bh(&client->lock); | |
c6c8fea2 | 117 | |
fb1f23ea | 118 | kfree(client); |
bd5b80d5 | 119 | module_put(THIS_MODULE); |
c6c8fea2 SE |
120 | |
121 | return 0; | |
122 | } | |
123 | ||
af4447f6 SE |
124 | static ssize_t batadv_socket_read(struct file *file, char __user *buf, |
125 | size_t count, loff_t *ppos) | |
c6c8fea2 | 126 | { |
56303d34 SE |
127 | struct batadv_socket_client *socket_client = file->private_data; |
128 | struct batadv_socket_packet *socket_packet; | |
c6c8fea2 SE |
129 | size_t packet_len; |
130 | int error; | |
131 | ||
825ffe1f | 132 | if ((file->f_flags & O_NONBLOCK) && socket_client->queue_len == 0) |
c6c8fea2 SE |
133 | return -EAGAIN; |
134 | ||
825ffe1f | 135 | if (!buf || count < sizeof(struct batadv_icmp_packet)) |
c6c8fea2 SE |
136 | return -EINVAL; |
137 | ||
c6c8fea2 SE |
138 | error = wait_event_interruptible(socket_client->queue_wait, |
139 | socket_client->queue_len); | |
140 | ||
141 | if (error) | |
142 | return error; | |
143 | ||
144 | spin_lock_bh(&socket_client->lock); | |
145 | ||
146 | socket_packet = list_first_entry(&socket_client->queue_list, | |
56303d34 | 147 | struct batadv_socket_packet, list); |
c6c8fea2 SE |
148 | list_del(&socket_packet->list); |
149 | socket_client->queue_len--; | |
150 | ||
151 | spin_unlock_bh(&socket_client->lock); | |
152 | ||
b5a1eeef SE |
153 | packet_len = min(count, socket_packet->icmp_len); |
154 | error = copy_to_user(buf, &socket_packet->icmp_packet, packet_len); | |
c6c8fea2 | 155 | |
c6c8fea2 SE |
156 | kfree(socket_packet); |
157 | ||
158 | if (error) | |
159 | return -EFAULT; | |
160 | ||
161 | return packet_len; | |
162 | } | |
163 | ||
af4447f6 SE |
164 | static ssize_t batadv_socket_write(struct file *file, const char __user *buff, |
165 | size_t len, loff_t *off) | |
c6c8fea2 | 166 | { |
56303d34 SE |
167 | struct batadv_socket_client *socket_client = file->private_data; |
168 | struct batadv_priv *bat_priv = socket_client->bat_priv; | |
169 | struct batadv_hard_iface *primary_if = NULL; | |
c6c8fea2 | 170 | struct sk_buff *skb; |
da6b8c20 SW |
171 | struct batadv_icmp_packet_rr *icmp_packet_rr; |
172 | struct batadv_icmp_header *icmp_header; | |
56303d34 SE |
173 | struct batadv_orig_node *orig_node = NULL; |
174 | struct batadv_neigh_node *neigh_node = NULL; | |
96412690 | 175 | size_t packet_len = sizeof(struct batadv_icmp_packet); |
6b5e971a | 176 | u8 *addr; |
c6c8fea2 | 177 | |
da6b8c20 | 178 | if (len < sizeof(struct batadv_icmp_header)) { |
39c75a51 | 179 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
1eda58bf | 180 | "Error - can't send packet from char device: invalid packet size\n"); |
c6c8fea2 SE |
181 | return -EINVAL; |
182 | } | |
183 | ||
e5d89254 | 184 | primary_if = batadv_primary_if_get_selected(bat_priv); |
32ae9b22 ML |
185 | |
186 | if (!primary_if) { | |
187 | len = -EFAULT; | |
188 | goto out; | |
189 | } | |
c6c8fea2 | 190 | |
da6b8c20 SW |
191 | if (len >= BATADV_ICMP_MAX_PACKET_SIZE) |
192 | packet_len = BATADV_ICMP_MAX_PACKET_SIZE; | |
193 | else | |
194 | packet_len = len; | |
c6c8fea2 | 195 | |
41ab6c48 | 196 | skb = netdev_alloc_skb_ip_align(NULL, packet_len + ETH_HLEN); |
32ae9b22 ML |
197 | if (!skb) { |
198 | len = -ENOMEM; | |
199 | goto out; | |
200 | } | |
c6c8fea2 | 201 | |
c54f38c9 | 202 | skb->priority = TC_PRIO_CONTROL; |
41ab6c48 | 203 | skb_reserve(skb, ETH_HLEN); |
4df864c1 | 204 | icmp_header = skb_put(skb, packet_len); |
c6c8fea2 | 205 | |
da6b8c20 | 206 | if (copy_from_user(icmp_header, buff, packet_len)) { |
c6c8fea2 SE |
207 | len = -EFAULT; |
208 | goto free_skb; | |
209 | } | |
210 | ||
a40d9b07 | 211 | if (icmp_header->packet_type != BATADV_ICMP) { |
39c75a51 | 212 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
1eda58bf | 213 | "Error - can't send packet from char device: got bogus packet type (expected: BAT_ICMP)\n"); |
c6c8fea2 SE |
214 | len = -EINVAL; |
215 | goto free_skb; | |
216 | } | |
217 | ||
da6b8c20 SW |
218 | switch (icmp_header->msg_type) { |
219 | case BATADV_ECHO_REQUEST: | |
220 | if (len < sizeof(struct batadv_icmp_packet)) { | |
221 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, | |
222 | "Error - can't send packet from char device: invalid packet size\n"); | |
223 | len = -EINVAL; | |
224 | goto free_skb; | |
225 | } | |
226 | ||
227 | if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) | |
228 | goto dst_unreach; | |
229 | ||
230 | orig_node = batadv_orig_hash_find(bat_priv, icmp_header->dst); | |
231 | if (!orig_node) | |
232 | goto dst_unreach; | |
233 | ||
7351a482 SW |
234 | neigh_node = batadv_orig_router_get(orig_node, |
235 | BATADV_IF_DEFAULT); | |
da6b8c20 SW |
236 | if (!neigh_node) |
237 | goto dst_unreach; | |
238 | ||
239 | if (!neigh_node->if_incoming) | |
240 | goto dst_unreach; | |
241 | ||
242 | if (neigh_node->if_incoming->if_status != BATADV_IF_ACTIVE) | |
243 | goto dst_unreach; | |
244 | ||
245 | icmp_packet_rr = (struct batadv_icmp_packet_rr *)icmp_header; | |
8fdd0153 AQ |
246 | if (packet_len == sizeof(*icmp_packet_rr)) { |
247 | addr = neigh_node->if_incoming->net_dev->dev_addr; | |
248 | ether_addr_copy(icmp_packet_rr->rr[0], addr); | |
249 | } | |
da6b8c20 SW |
250 | |
251 | break; | |
252 | default: | |
39c75a51 | 253 | batadv_dbg(BATADV_DBG_BATMAN, bat_priv, |
da6b8c20 | 254 | "Error - can't send packet from char device: got unknown message type\n"); |
c6c8fea2 SE |
255 | len = -EINVAL; |
256 | goto free_skb; | |
257 | } | |
258 | ||
da6b8c20 | 259 | icmp_header->uid = socket_client->index; |
c6c8fea2 | 260 | |
a40d9b07 | 261 | if (icmp_header->version != BATADV_COMPAT_VERSION) { |
da6b8c20 | 262 | icmp_header->msg_type = BATADV_PARAMETER_PROBLEM; |
a40d9b07 | 263 | icmp_header->version = BATADV_COMPAT_VERSION; |
da6b8c20 | 264 | batadv_socket_add_packet(socket_client, icmp_header, |
af4447f6 | 265 | packet_len); |
c6c8fea2 SE |
266 | goto free_skb; |
267 | } | |
268 | ||
8fdd0153 | 269 | ether_addr_copy(icmp_header->orig, primary_if->net_dev->dev_addr); |
c6c8fea2 | 270 | |
95d39278 | 271 | batadv_send_unicast_skb(skb, neigh_node); |
c6c8fea2 SE |
272 | goto out; |
273 | ||
c6c8fea2 | 274 | dst_unreach: |
da6b8c20 SW |
275 | icmp_header->msg_type = BATADV_DESTINATION_UNREACHABLE; |
276 | batadv_socket_add_packet(socket_client, icmp_header, packet_len); | |
c6c8fea2 SE |
277 | free_skb: |
278 | kfree_skb(skb); | |
279 | out: | |
32ae9b22 | 280 | if (primary_if) |
82047ad7 | 281 | batadv_hardif_put(primary_if); |
44524fcd | 282 | if (neigh_node) |
25bb2509 | 283 | batadv_neigh_node_put(neigh_node); |
44524fcd | 284 | if (orig_node) |
5d967310 | 285 | batadv_orig_node_put(orig_node); |
c6c8fea2 SE |
286 | return len; |
287 | } | |
288 | ||
ade994f4 | 289 | static __poll_t batadv_socket_poll(struct file *file, poll_table *wait) |
c6c8fea2 | 290 | { |
56303d34 | 291 | struct batadv_socket_client *socket_client = file->private_data; |
c6c8fea2 SE |
292 | |
293 | poll_wait(file, &socket_client->queue_wait, wait); | |
294 | ||
295 | if (socket_client->queue_len > 0) | |
a9a08845 | 296 | return EPOLLIN | EPOLLRDNORM; |
c6c8fea2 SE |
297 | |
298 | return 0; | |
299 | } | |
300 | ||
af4447f6 | 301 | static const struct file_operations batadv_fops = { |
c6c8fea2 | 302 | .owner = THIS_MODULE, |
af4447f6 SE |
303 | .open = batadv_socket_open, |
304 | .release = batadv_socket_release, | |
305 | .read = batadv_socket_read, | |
306 | .write = batadv_socket_write, | |
307 | .poll = batadv_socket_poll, | |
c6c8fea2 SE |
308 | .llseek = no_llseek, |
309 | }; | |
310 | ||
ff15c27c SE |
311 | /** |
312 | * batadv_socket_setup() - Create debugfs "socket" file | |
313 | * @bat_priv: the bat priv with all the soft interface information | |
ff15c27c | 314 | */ |
3bcacd1e | 315 | void batadv_socket_setup(struct batadv_priv *bat_priv) |
c6c8fea2 | 316 | { |
3bcacd1e GKH |
317 | debugfs_create_file(BATADV_ICMP_SOCKET, 0600, bat_priv->debug_dir, |
318 | bat_priv, &batadv_fops); | |
c6c8fea2 SE |
319 | } |
320 | ||
da6b8c20 | 321 | /** |
7e9a8c2c | 322 | * batadv_socket_add_packet() - schedule an icmp packet to be sent to |
34473822 | 323 | * userspace on an icmp socket. |
da6b8c20 SW |
324 | * @socket_client: the socket this packet belongs to |
325 | * @icmph: pointer to the header of the icmp packet | |
326 | * @icmp_len: total length of the icmp packet | |
327 | */ | |
56303d34 | 328 | static void batadv_socket_add_packet(struct batadv_socket_client *socket_client, |
da6b8c20 | 329 | struct batadv_icmp_header *icmph, |
af4447f6 | 330 | size_t icmp_len) |
c6c8fea2 | 331 | { |
56303d34 | 332 | struct batadv_socket_packet *socket_packet; |
da6b8c20 | 333 | size_t len; |
c6c8fea2 | 334 | |
704509b8 | 335 | socket_packet = kmalloc(sizeof(*socket_packet), GFP_ATOMIC); |
c6c8fea2 SE |
336 | |
337 | if (!socket_packet) | |
338 | return; | |
339 | ||
da6b8c20 SW |
340 | len = icmp_len; |
341 | /* check the maximum length before filling the buffer */ | |
342 | if (len > sizeof(socket_packet->icmp_packet)) | |
343 | len = sizeof(socket_packet->icmp_packet); | |
344 | ||
c6c8fea2 | 345 | INIT_LIST_HEAD(&socket_packet->list); |
da6b8c20 SW |
346 | memcpy(&socket_packet->icmp_packet, icmph, len); |
347 | socket_packet->icmp_len = len; | |
c6c8fea2 SE |
348 | |
349 | spin_lock_bh(&socket_client->lock); | |
350 | ||
351 | /* while waiting for the lock the socket_client could have been | |
9cfc7bd6 SE |
352 | * deleted |
353 | */ | |
da6b8c20 | 354 | if (!batadv_socket_client_hash[icmph->uid]) { |
c6c8fea2 SE |
355 | spin_unlock_bh(&socket_client->lock); |
356 | kfree(socket_packet); | |
357 | return; | |
358 | } | |
359 | ||
360 | list_add_tail(&socket_packet->list, &socket_client->queue_list); | |
361 | socket_client->queue_len++; | |
362 | ||
363 | if (socket_client->queue_len > 100) { | |
364 | socket_packet = list_first_entry(&socket_client->queue_list, | |
56303d34 SE |
365 | struct batadv_socket_packet, |
366 | list); | |
c6c8fea2 SE |
367 | |
368 | list_del(&socket_packet->list); | |
369 | kfree(socket_packet); | |
370 | socket_client->queue_len--; | |
371 | } | |
372 | ||
373 | spin_unlock_bh(&socket_client->lock); | |
374 | ||
375 | wake_up(&socket_client->queue_wait); | |
376 | } | |
377 | ||
da6b8c20 | 378 | /** |
7e9a8c2c | 379 | * batadv_socket_receive_packet() - schedule an icmp packet to be received |
da6b8c20 SW |
380 | * locally and sent to userspace. |
381 | * @icmph: pointer to the header of the icmp packet | |
382 | * @icmp_len: total length of the icmp packet | |
383 | */ | |
384 | void batadv_socket_receive_packet(struct batadv_icmp_header *icmph, | |
9039dc7e | 385 | size_t icmp_len) |
c6c8fea2 | 386 | { |
56303d34 | 387 | struct batadv_socket_client *hash; |
c6c8fea2 | 388 | |
da6b8c20 | 389 | hash = batadv_socket_client_hash[icmph->uid]; |
c6c8fea2 | 390 | if (hash) |
da6b8c20 | 391 | batadv_socket_add_packet(hash, icmph, icmp_len); |
c6c8fea2 | 392 | } |