blob: 7f61183654e2f6f42f0c1b276b656c049c7a6601 [file] [log] [blame]
Pau Espin Pedrol51e9dde2023-01-24 17:34:34 +01001
2/* TUN interface functions.
3 * (C) 2023 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
4 * Author: Pau Espin Pedrol <pespin@sysmocom.de>
5 *
6 * All Rights Reserved
7 *
8 * SPDX-License-Identifier: GPL-2.0+
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 */
21
22#include "config.h"
23
24/*! \addtogroup tun
25 * @{
26 * tun network device (interface) convenience functions
27 *
28 * \file tundev.c
29 *
30 * Example lifecycle use of the API:
31 *
32 * struct osmo_sockaddr_str osa_str = {};
33 * struct osmo_sockaddr osa = {};
34 *
35 * // Allocate object:
36 * struct osmo_tundev *tundev = osmo_tundev_alloc(parent_talloc_ctx, name);
37 * OSMO_ASSERT(tundev);
38 *
39 * // Configure object (before opening):
40 * osmo_tundev_set_data_ind_cb(tun, tun_data_ind_cb);
41 * rc = osmo_tundev_set_dev_name(tun, "mytunnel0");
42 * rc = osmo_tundev_set_netns_name(tun, "some_netns_name_or_null");
43 *
44 * // Open the tundev object:
45 * rc = osmo_tundev_open(tundev);
46 * // The tunnel device is now created and an associatd netdev object
47 * // is available to operate the device:
48 * struct osmo_netdev *netdev = osmo_tundev_get_netdev(tundev);
49 * OSMO_ASSERT(netdev);
50 *
51 * // Add a local IPv4 address:
52 * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1");
53 * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
54 * rc = osmo_netdev_add_addr(netdev, &osa, 24);
55 *
56 * // Bring network interface up:
57 * rc = osmo_netdev_ifupdown(netdev, true);
58 *
59 * // Add default route (0.0.0.0/0):
60 * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0");
61 * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
62 * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL);
63 *
64 * // Close the tunnel (asssociated netdev object becomes unavailable)
65 * rc = osmo_tundev_close(tundev);
66 * // Free the object:
67 * osmo_tundev_free(tundev);
68 */
69
70#if (!EMBEDDED)
71
72#include <stdio.h>
73#include <stdlib.h>
74#include <stdio.h>
75#include <fcntl.h>
76#include <unistd.h>
77#include <errno.h>
78#include <ifaddrs.h>
79#include <sys/types.h>
80#include <sys/time.h>
81#include <sys/ioctl.h>
82#include <sys/socket.h>
83#include <sys/stat.h>
84#include <sys/time.h>
85#include <net/if.h>
86
87#if defined(__linux__)
88#include <linux/if_tun.h>
89#else
90#error "Unknown platform!"
91#endif
92
93#include <osmocom/core/utils.h>
94#include <osmocom/core/select.h>
95#include <osmocom/core/linuxlist.h>
96#include <osmocom/core/logging.h>
97#include <osmocom/core/write_queue.h>
98#include <osmocom/core/socket.h>
99#include <osmocom/core/netns.h>
100#include <osmocom/core/netdev.h>
101#include <osmocom/core/tun.h>
102
103#define TUN_DEV_PATH "/dev/net/tun"
104#define TUN_PACKET_MAX 8196
105
106#define LOGTUN(tun, lvl, fmt, args ...) \
107 LOGP(DLGLOBAL, lvl, "TUN(%s,if=%s/%u,ns=%s): " fmt, \
108 (tun)->name, (tun)->dev_name ? : "", \
109 (tun)->ifindex, (tun)->netns_name ? : "", ## args)
110
111struct osmo_tundev {
112 /* Name used to identify the osmo_tundev */
113 char *name;
114
115 /* netdev managing the tun interface: */
116 struct osmo_netdev *netdev;
117
118 /* ifindiex of the currently opened tunnel interface */
119 unsigned int ifindex;
120
121 /* Network interface name to use when setting up the tun device.
122 * NULL = let the system pick one. */
123 char *dev_name;
124 /* Whether dev_name is set by user or dynamically allocated by system */
125 bool dev_name_dynamic;
126
127 /* Write queue used since tun fd is set non-blocking */
128 struct osmo_wqueue wqueue;
129
130 /* netns name where the tun interface is created (NULL = default netns) */
131 char *netns_name;
132
133 /* API user private data */
134 void *priv_data;
135
136 /* Called by tundev each time a new packet is received on the tun interface. Can be NULL. */
137 osmo_tundev_data_ind_cb_t data_ind_cb;
138
139 /* Whether the tundev is in opened state (managing the tun interface) */
140 bool opened;
141};
142
143/* A new pkt arrived from the tun device, dispatch it to the API user */
144static int tundev_decaps(struct osmo_tundev *tundev)
145{
146 struct msgb *msg;
147 int rc;
148
149 msg = msgb_alloc(TUN_PACKET_MAX, "tundev_rx");
150
151 if ((rc = read(tundev->wqueue.bfd.fd, msgb_data(msg), TUN_PACKET_MAX)) <= 0) {
152 LOGTUN(tundev, LOGL_ERROR, "read() failed: %s (%d)\n", strerror(errno), errno);
153 msgb_free(msg);
154 return -1;
155 }
156 msgb_put(msg, rc);
157
158 if (tundev->data_ind_cb)
159 return tundev->data_ind_cb(tundev, msg);
160
161 msgb_free(msg);
162 return 0;
163}
164
165/* callback for tun device osmocom select loop integration */
166static int tundev_read_cb(struct osmo_fd *fd)
167{
168 struct osmo_tundev *tundev = fd->data;
169 return tundev_decaps(tundev);
170}
171
172/* callback for tun device osmocom select loop integration */
173static int tundev_write_cb(struct osmo_fd *fd, struct msgb *msg)
174{
175 struct osmo_tundev *tundev = fd->data;
176 size_t pkt_len = msgb_length(msg);
177
178 int rc;
179 rc = write(tundev->wqueue.bfd.fd, msgb_data(msg), pkt_len);
180 if (rc < 0)
181 LOGTUN(tundev, LOGL_ERROR, "write() failed: %s (%d)\n", strerror(errno), errno);
182 else if (rc < pkt_len)
183 LOGTUN(tundev, LOGL_ERROR, "short write() %d < %zu\n", rc, pkt_len);
184 return rc;
185}
186
187static int tundev_ifupdown_ind_cb(struct osmo_netdev *netdev, bool ifupdown)
188{
189 struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
190 LOGTUN(tundev, LOGL_NOTICE, "Physical link state changed: %s\n",
191 ifupdown ? "UP" : "DOWN");
192
193 /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes
194 * sense to get one out of the door ASAP. */
195 osmo_wqueue_clear(&tundev->wqueue);
196 return 0;
197}
198
199static int tundev_dev_name_chg_cb(struct osmo_netdev *netdev, const char *new_dev_name)
200{
201 struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
202 LOGTUN(tundev, LOGL_NOTICE, "netdev changed name: %s -> %s\n",
203 osmo_netdev_get_dev_name(netdev), new_dev_name);
204
205 if (tundev->dev_name_dynamic) {
206 osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name);
207 } else {
208 /* TODO: in here we probably want to force the iface name back
209 * to tundev->dev_name one we have a osmo_netdev_set_ifname() API */
210 osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name);
211 }
212
213 return 0;
214}
215
216static int tundev_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu)
217{
218 struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
219 LOGTUN(tundev, LOGL_NOTICE, "netdev changed MTU: %u\n", new_mtu);
220
221 return 0;
222}
223
224/*! Allocate a new tundev object.
225 * \param[in] ctx talloc context to use as a parent when allocating the tundev object
226 * \param[in] name A name providen to identify the tundev object
227 * \returns newly allocated tundev object on success; NULL on error
228 */
229struct osmo_tundev *osmo_tundev_alloc(void *ctx, const char *name)
230{
231 struct osmo_tundev *tundev;
232
233 tundev = talloc_zero(ctx, struct osmo_tundev);
234 if (!tundev)
235 return NULL;
236
237 tundev->netdev = osmo_netdev_alloc(tundev, name);
238 if (!tundev->netdev) {
239 talloc_free(tundev);
240 return NULL;
241 }
242 osmo_netdev_set_priv_data(tundev->netdev, tundev);
243 osmo_netdev_set_ifupdown_ind_cb(tundev->netdev, tundev_ifupdown_ind_cb);
244 osmo_netdev_set_dev_name_chg_cb(tundev->netdev, tundev_dev_name_chg_cb);
245 osmo_netdev_set_mtu_chg_cb(tundev->netdev, tundev_mtu_chg_cb);
246
247 tundev->name = talloc_strdup(tundev, name);
248 osmo_wqueue_init(&tundev->wqueue, 1000);
249 osmo_fd_setup(&tundev->wqueue.bfd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, tundev, 0);
250 tundev->wqueue.read_cb = tundev_read_cb;
251 tundev->wqueue.write_cb = tundev_write_cb;
252
253 return tundev;
254}
255
256/*! Free an allocated tundev object.
257 * \param[in] tundev The tundev object to free
258 */
259void osmo_tundev_free(struct osmo_tundev *tundev)
260{
261 if (!tundev)
262 return;
263 osmo_tundev_close(tundev);
264 osmo_netdev_free(tundev->netdev);
265 talloc_free(tundev);
266}
267
268/*! Open and configure fd of the tunnel device.
269 * \param[in] tundev The tundev object whose tunnel interface to open
270 * \param[in] flags internal linux flags to pass when creating the device (not used yet)
271 * \returns 0 on success; negative on error
272 */
273static int tundev_open_fd(struct osmo_tundev *tundev, int flags)
274{
275 struct ifreq ifr;
276 int fd, rc;
277
278 fd = open(TUN_DEV_PATH, O_RDWR);
279 if (fd < 0) {
280 LOGTUN(tundev, LOGL_ERROR, "Cannot open " TUN_DEV_PATH ": %s\n", strerror(errno));
281 return fd;
282 }
283
284 memset(&ifr, 0, sizeof(ifr));
285 ifr.ifr_flags = IFF_TUN | IFF_NO_PI | flags;
286 if (tundev->dev_name) {
287 /* if a TUN interface name was specified, put it in the structure; otherwise,
288 the kernel will try to allocate the "next" device of the specified type */
289 osmo_strlcpy(ifr.ifr_name, tundev->dev_name, IFNAMSIZ);
290 }
291
292 /* try to create the device */
293 rc = ioctl(fd, TUNSETIFF, (void *) &ifr);
294 if (rc < 0)
295 goto close_ret;
296
297 /* Read name back from device */
298 if (!tundev->dev_name) {
299 ifr.ifr_name[IFNAMSIZ - 1] = '\0';
300 tundev->dev_name = talloc_strdup(tundev, ifr.ifr_name);
301 tundev->dev_name_dynamic = true;
302 }
303
304 /* Store interface index:
305 * (Note: there's a potential race condition here between creating the
306 * iface with a given name above and attempting to retrieve its ifindex based
307 * on that name. Someone (ie udev) could have the iface renamed in
308 * between here. It's a pity that TUNSETIFF doesn't copy back to us the
309 * newly allocated ifinidex as it does with ifname)
310 */
311 tundev->ifindex = if_nametoindex(tundev->dev_name);
312 if (tundev->ifindex == 0) {
313 LOGTUN(tundev, LOGL_ERROR, "Unable to find ifinidex for dev %s\n",
314 tundev->dev_name);
315 rc = -ENODEV;
316 goto close_ret;
317 }
318
319 LOGTUN(tundev, LOGL_INFO, "TUN device created\n");
320
321 /* set non-blocking: */
322 rc = fcntl(fd, F_GETFL);
323 if (rc < 0) {
324 LOGTUN(tundev, LOGL_ERROR, "fcntl(F_GETFL) failed: %s (%d)\n",
325 strerror(errno), errno);
326 goto close_ret;
327 }
328 rc = fcntl(fd, F_SETFL, rc | O_NONBLOCK);
329 if (rc < 0) {
330 LOGTUN(tundev, LOGL_ERROR, "fcntl(F_SETFL, O_NONBLOCK) failed: %s (%d)\n",
331 strerror(errno), errno);
332 goto close_ret;
333 }
334 return fd;
335
336close_ret:
337 close(fd);
338 return rc;
339}
340
341/*! Open the tunnel device owned by the tundev object.
342 * \param[in] tundev The tundev object to open
343 * \returns 0 on success; negative on error
344 */
345int osmo_tundev_open(struct osmo_tundev *tundev)
346{
347 struct osmo_netns_switch_state switch_state;
348 int rc;
349 int netns_fd = -1;
350
351 if (tundev->opened)
352 return -EALREADY;
353
354 /* temporarily switch to specified namespace to create tun device */
355 if (tundev->netns_name) {
356 LOGTUN(tundev, LOGL_INFO, "Open tun: Switch to netns '%s'\n",
357 tundev->netns_name);
358 netns_fd = osmo_netns_open_fd(tundev->netns_name);
359 if (netns_fd < 0) {
360 LOGP(DLGLOBAL, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n",
361 tundev->netns_name, strerror(errno), errno);
362 return netns_fd;
363 }
364 rc = osmo_netns_switch_enter(netns_fd, &switch_state);
365 if (rc < 0) {
366 LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n",
367 tundev->netns_name, strerror(errno), errno);
368 goto err_close_netns_fd;
369 }
370 }
371
372 tundev->wqueue.bfd.fd = tundev_open_fd(tundev, 0);
373 if (tundev->wqueue.bfd.fd < 0) {
374 LOGTUN(tundev, LOGL_ERROR, "Cannot open TUN device: %s\n", strerror(errno));
375 rc = -ENODEV;
376 goto err_restore_ns;
377 }
378
379 /* switch back to default namespace */
380 if (tundev->netns_name) {
381 rc = osmo_netns_switch_exit(&switch_state);
382 if (rc < 0) {
383 LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch back from netns '%s': %s\n",
384 tundev->netns_name, strerror(errno));
385 goto err_close_tun;
386 }
387 LOGTUN(tundev, LOGL_INFO, "Open tun: Back from netns '%s'\n",
388 tundev->netns_name);
389 }
390
391 rc = osmo_netdev_set_netns_name(tundev->netdev, tundev->netns_name);
392 if (rc < 0)
393 goto err_close_tun;
394 rc = osmo_netdev_set_ifindex(tundev->netdev, tundev->ifindex);
395 if (rc < 0)
396 goto err_close_tun;
397
398 rc = osmo_netdev_register(tundev->netdev);
399 if (rc < 0)
400 goto err_close_tun;
401
402 osmo_fd_register(&tundev->wqueue.bfd);
403 tundev->opened = true;
404 return 0;
405
406err_close_tun:
407 close(tundev->wqueue.bfd.fd);
408 tundev->wqueue.bfd.fd = -1;
409err_restore_ns:
410 osmo_netns_switch_exit(&switch_state);
411err_close_netns_fd:
412 if (netns_fd >= 0)
413 close(netns_fd);
414 return rc;
415}
416
417/*! Close the tunnel device owned by the tundev object.
418 * \param[in] tundev The tundev object to close
419 * \returns 0 on success; negative on error
420 */
421int osmo_tundev_close(struct osmo_tundev *tundev)
422{
423 if (!tundev->opened)
424 return -EALREADY;
425
426 osmo_wqueue_clear(&tundev->wqueue);
427 if (tundev->wqueue.bfd.fd != -1) {
428 osmo_fd_unregister(&tundev->wqueue.bfd);
429 close(tundev->wqueue.bfd.fd);
430 tundev->wqueue.bfd.fd = -1;
431 }
432
433 osmo_netdev_unregister(tundev->netdev);
434 if (tundev->dev_name_dynamic) {
435 TALLOC_FREE(tundev->dev_name);
436 tundev->dev_name_dynamic = false;
437 }
438 tundev->opened = false;
439 return 0;
440}
441
442/*! Retrieve whether the tundev object is in "opened" state.
443 * \param[in] tundev The tundev object to check
444 * \returns true if in state "opened"; false otherwise
445 */
446bool osmo_tundev_is_open(struct osmo_tundev *tundev)
447{
448 return tundev->opened;
449}
450
451/*! Set private user data pointer on the tundev object.
452 * \param[in] tundev The tundev object where the field is set
453 */
454void osmo_tundev_set_priv_data(struct osmo_tundev *tundev, void *priv_data)
455{
456 tundev->priv_data = priv_data;
457}
458
459/*! Get private user data pointer from the tundev object.
460 * \param[in] tundev The tundev object from where to retrieve the field
461 * \returns The current value of the priv_data field.
462 */
463void *osmo_tundev_get_priv_data(struct osmo_tundev *tundev)
464{
465 return tundev->priv_data;
466}
467
468/*! Set data_ind_cb callback, called when a new packet is received on the tun interface.
469 * \param[in] tundev The tundev object where the field is set
470 * \param[in] data_ind_cb the user provided function to be called when a new packet is received
471 */
472void osmo_tundev_set_data_ind_cb(struct osmo_tundev *tundev, osmo_tundev_data_ind_cb_t data_ind_cb)
473{
474 tundev->data_ind_cb = data_ind_cb;
475}
476
477/*! Get name used to identify the tundev object.
478 * \param[in] tundev The tundev object from where to retrieve the field
479 * \returns The current value of the name used to identify the tundev object
480 */
481const char *osmo_tundev_get_name(const struct osmo_tundev *tundev)
482{
483 return tundev->name;
484}
485
486/*! Set name used to name the tunnel interface created by the tundev object.
487 * \param[in] tundev The tundev object where the field is set
488 * \param[in] dev_name The tunnel interface name to use
489 * \returns 0 on success; negative on error
490 *
491 * This is used during osmo_tundev_open() time, and hence shouldn't be changed
492 * when the tundev object is in "opened" state.
493 * If left as NULL (default), the system will pick a suitable name during
494 * osmo_tundev_open(), and the field will be updated to the system-selected
495 * name, which can be retrieved later with osmo_tundev_get_dev_name().
496 */
497int osmo_tundev_set_dev_name(struct osmo_tundev *tundev, const char *dev_name)
498{
499 if (tundev->opened)
500 return -EALREADY;
501 osmo_talloc_replace_string(tundev, &tundev->dev_name, dev_name);
502 tundev->dev_name_dynamic = false;
503 return 0;
504}
505
506/*! Get name used to name the tunnel interface created by the tundev object
507 * \param[in] tundev The tundev object from where to retrieve the field
508 * \returns The current value of the configured tunnel interface name to use
509 */
510const char *osmo_tundev_get_dev_name(const struct osmo_tundev *tundev)
511{
512 return tundev->dev_name;
513}
514
515/*! Set name of the network namespace to use when opening the tunnel interface
516 * \param[in] tundev The tundev object where the field is set
517 * \param[in] netns_name The network namespace to use during tunnel interface creation
518 * \returns 0 on success; negative on error
519 *
520 * This is used during osmo_tundev_open() time, and hence shouldn't be changed
521 * when the tundev object is in "opened" state.
522 * If left as NULL (default), the system will stay in the current network namespace.
523 */
524int osmo_tundev_set_netns_name(struct osmo_tundev *tundev, const char *netns_name)
525{
526 if (tundev->opened)
527 return -EALREADY;
528 osmo_talloc_replace_string(tundev, &tundev->netns_name, netns_name);
529 return 0;
530}
531
532/*! Get name of network namespace used when opening the tunnel interface
533 * \param[in] tundev The tundev object from where to retrieve the field
534 * \returns The current value of the configured network namespace
535 */
536const char *osmo_tundev_get_netns_name(const struct osmo_tundev *tundev)
537{
538 return tundev->netns_name;
539}
540
541/*! Get netdev managing the tunnel interface of tundev
542 * \param[in] tundev The tundev object from where to retrieve the field
543 * \returns The netdev objet managing the tun interface
544 */
545struct osmo_netdev *osmo_tundev_get_netdev(struct osmo_tundev *tundev)
546{
547 return tundev->netdev;
548}
549
550/*! Submit a packet to the tunnel device managed by the tundev object
551 * \param[in] tundev The tundev object owning the tunnel device where to inject the packet
552 * \param[in] msg The msgb containg the packet to transfer
553 * \returns The current value of the configured network namespace
554 *
555 * This function takes the ownership of msg in all cases.
556 */
557int osmo_tundev_send(struct osmo_tundev *tundev, struct msgb *msg)
558{
559 int rc = osmo_wqueue_enqueue(&tundev->wqueue, msg);
560 if (rc < 0) {
561 LOGTUN(tundev, LOGL_ERROR, "Failed to enqueue the packet\n");
562 msgb_free(msg);
563 return rc;
564 }
565 return rc;
566}
567
568
569#endif /* (!EMBEDDED) */
570
571/*! @} */