mirror of
https://git.proxmox.com/git/mirror_iproute2
synced 2025-10-06 08:45:57 +00:00

The original problem was that something like: | strncpy(ifr.ifr_name, *argv, IFNAMSIZ); might leave ifr.ifr_name unterminated if length of *argv exceeds IFNAMSIZ. In order to fix this, I thought about replacing all those cases with (equivalent) calls to snprintf() or even introducing strlcpy(). But as Ulrich Drepper correctly pointed out when rejecting the latter from being added to glibc, truncating a string without notifying the user is not to be considered good practice. So let's excercise what he suggested and reject empty, overlong or otherwise invalid interface names right from the start - this way calls to strncpy() like shown above become safe and the user has a chance to reconsider what he was trying to do. Note that this doesn't add calls to check_ifname() to all places where user supplied interface name is parsed. In many cases, the interface must exist already and is therefore looked up using ll_name_to_index(), so if_nametoindex() will perform the necessary checks already. Signed-off-by: Phil Sutter <phil@nwl.cc>
443 lines
8.9 KiB
C
443 lines
8.9 KiB
C
/*
|
|
* iptunnel.c "ip tuntap"
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
* Authors: David Woodhouse <David.Woodhouse@intel.com>
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/ioctl.h>
|
|
#include <linux/if.h>
|
|
#include <linux/if_tun.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <glob.h>
|
|
|
|
#include "rt_names.h"
|
|
#include "utils.h"
|
|
#include "ip_common.h"
|
|
|
|
#define TUNDEV "/dev/net/tun"
|
|
|
|
static void usage(void) __attribute__((noreturn));
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: ip tuntap { add | del | show | list | lst | help } [ dev PHYS_DEV ]\n");
|
|
fprintf(stderr, " [ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n");
|
|
fprintf(stderr, " [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]\n");
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "Where: USER := { STRING | NUMBER }\n");
|
|
fprintf(stderr, " GROUP := { STRING | NUMBER }\n");
|
|
exit(-1);
|
|
}
|
|
|
|
static int tap_add_ioctl(struct ifreq *ifr, uid_t uid, gid_t gid)
|
|
{
|
|
int fd;
|
|
int ret = -1;
|
|
|
|
#ifdef IFF_TUN_EXCL
|
|
ifr->ifr_flags |= IFF_TUN_EXCL;
|
|
#endif
|
|
|
|
fd = open(TUNDEV, O_RDWR);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return -1;
|
|
}
|
|
if (ioctl(fd, TUNSETIFF, ifr)) {
|
|
perror("ioctl(TUNSETIFF)");
|
|
goto out;
|
|
}
|
|
if (uid != -1 && ioctl(fd, TUNSETOWNER, uid)) {
|
|
perror("ioctl(TUNSETOWNER)");
|
|
goto out;
|
|
}
|
|
if (gid != -1 && ioctl(fd, TUNSETGROUP, gid)) {
|
|
perror("ioctl(TUNSETGROUP)");
|
|
goto out;
|
|
}
|
|
if (ioctl(fd, TUNSETPERSIST, 1)) {
|
|
perror("ioctl(TUNSETPERSIST)");
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
out:
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
static int tap_del_ioctl(struct ifreq *ifr)
|
|
{
|
|
int fd = open(TUNDEV, O_RDWR);
|
|
int ret = -1;
|
|
|
|
if (fd < 0) {
|
|
perror("open");
|
|
return -1;
|
|
}
|
|
if (ioctl(fd, TUNSETIFF, ifr)) {
|
|
perror("ioctl(TUNSETIFF)");
|
|
goto out;
|
|
}
|
|
if (ioctl(fd, TUNSETPERSIST, 0)) {
|
|
perror("ioctl(TUNSETPERSIST)");
|
|
goto out;
|
|
}
|
|
ret = 0;
|
|
out:
|
|
close(fd);
|
|
return ret;
|
|
|
|
}
|
|
static int parse_args(int argc, char **argv,
|
|
struct ifreq *ifr, uid_t *uid, gid_t *gid)
|
|
{
|
|
int count = 0;
|
|
|
|
memset(ifr, 0, sizeof(*ifr));
|
|
|
|
ifr->ifr_flags |= IFF_NO_PI;
|
|
|
|
while (argc > 0) {
|
|
if (matches(*argv, "mode") == 0) {
|
|
NEXT_ARG();
|
|
if (matches(*argv, "tun") == 0) {
|
|
if (ifr->ifr_flags & IFF_TAP) {
|
|
fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
|
|
exit(-1);
|
|
}
|
|
ifr->ifr_flags |= IFF_TUN;
|
|
} else if (matches(*argv, "tap") == 0) {
|
|
if (ifr->ifr_flags & IFF_TUN) {
|
|
fprintf(stderr, "You managed to ask for more than one tunnel mode.\n");
|
|
exit(-1);
|
|
}
|
|
ifr->ifr_flags |= IFF_TAP;
|
|
} else {
|
|
fprintf(stderr, "Unknown tunnel mode \"%s\"\n", *argv);
|
|
exit(-1);
|
|
}
|
|
} else if (uid && matches(*argv, "user") == 0) {
|
|
char *end;
|
|
unsigned long user;
|
|
|
|
NEXT_ARG();
|
|
if (**argv && ((user = strtol(*argv, &end, 10)), !*end))
|
|
*uid = user;
|
|
else {
|
|
struct passwd *pw = getpwnam(*argv);
|
|
|
|
if (!pw) {
|
|
fprintf(stderr, "invalid user \"%s\"\n", *argv);
|
|
exit(-1);
|
|
}
|
|
*uid = pw->pw_uid;
|
|
}
|
|
} else if (gid && matches(*argv, "group") == 0) {
|
|
char *end;
|
|
unsigned long group;
|
|
|
|
NEXT_ARG();
|
|
|
|
if (**argv && ((group = strtol(*argv, &end, 10)), !*end))
|
|
*gid = group;
|
|
else {
|
|
struct group *gr = getgrnam(*argv);
|
|
|
|
if (!gr) {
|
|
fprintf(stderr, "invalid group \"%s\"\n", *argv);
|
|
exit(-1);
|
|
}
|
|
*gid = gr->gr_gid;
|
|
}
|
|
} else if (matches(*argv, "pi") == 0) {
|
|
ifr->ifr_flags &= ~IFF_NO_PI;
|
|
} else if (matches(*argv, "one_queue") == 0) {
|
|
ifr->ifr_flags |= IFF_ONE_QUEUE;
|
|
} else if (matches(*argv, "vnet_hdr") == 0) {
|
|
ifr->ifr_flags |= IFF_VNET_HDR;
|
|
} else if (matches(*argv, "multi_queue") == 0) {
|
|
ifr->ifr_flags |= IFF_MULTI_QUEUE;
|
|
} else if (matches(*argv, "dev") == 0) {
|
|
NEXT_ARG();
|
|
if (get_ifname(ifr->ifr_name, *argv))
|
|
invarg("\"dev\" not a valid ifname", *argv);
|
|
} else {
|
|
if (matches(*argv, "name") == 0) {
|
|
NEXT_ARG();
|
|
} else if (matches(*argv, "help") == 0)
|
|
usage();
|
|
if (ifr->ifr_name[0])
|
|
duparg2("name", *argv);
|
|
if (get_ifname(ifr->ifr_name, *argv))
|
|
invarg("\"name\" not a valid ifname", *argv);
|
|
}
|
|
count++;
|
|
argc--; argv++;
|
|
}
|
|
|
|
if (!(ifr->ifr_flags & TUN_TYPE_MASK)) {
|
|
fprintf(stderr, "You failed to specify a tunnel mode\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int do_add(int argc, char **argv)
|
|
{
|
|
struct ifreq ifr;
|
|
uid_t uid = -1;
|
|
gid_t gid = -1;
|
|
|
|
if (parse_args(argc, argv, &ifr, &uid, &gid) < 0)
|
|
return -1;
|
|
|
|
return tap_add_ioctl(&ifr, uid, gid);
|
|
}
|
|
|
|
static int do_del(int argc, char **argv)
|
|
{
|
|
struct ifreq ifr;
|
|
|
|
if (parse_args(argc, argv, &ifr, NULL, NULL) < 0)
|
|
return -1;
|
|
|
|
return tap_del_ioctl(&ifr);
|
|
}
|
|
|
|
static int read_prop(char *dev, char *prop, long *value)
|
|
{
|
|
char fname[IFNAMSIZ+25], buf[80], *endp;
|
|
ssize_t len;
|
|
int fd;
|
|
long result;
|
|
|
|
sprintf(fname, "/sys/class/net/%s/%s", dev, prop);
|
|
fd = open(fname, O_RDONLY);
|
|
if (fd < 0) {
|
|
if (strcmp(prop, "tun_flags"))
|
|
fprintf(stderr, "open %s: %s\n", fname,
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
len = read(fd, buf, sizeof(buf)-1);
|
|
close(fd);
|
|
if (len < 0) {
|
|
fprintf(stderr, "read %s: %s", fname, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
buf[len] = 0;
|
|
result = strtol(buf, &endp, 0);
|
|
if (*endp != '\n') {
|
|
fprintf(stderr, "Failed to parse %s\n", fname);
|
|
return -1;
|
|
}
|
|
*value = result;
|
|
return 0;
|
|
}
|
|
|
|
static void print_flags(long flags)
|
|
{
|
|
if (flags & IFF_TUN)
|
|
printf(" tun");
|
|
|
|
if (flags & IFF_TAP)
|
|
printf(" tap");
|
|
|
|
if (!(flags & IFF_NO_PI))
|
|
printf(" pi");
|
|
|
|
if (flags & IFF_ONE_QUEUE)
|
|
printf(" one_queue");
|
|
|
|
if (flags & IFF_VNET_HDR)
|
|
printf(" vnet_hdr");
|
|
|
|
flags &= ~(IFF_TUN|IFF_TAP|IFF_NO_PI|IFF_ONE_QUEUE|IFF_VNET_HDR);
|
|
if (flags)
|
|
printf(" UNKNOWN_FLAGS:%lx", flags);
|
|
}
|
|
|
|
static char *pid_name(pid_t pid)
|
|
{
|
|
char *comm;
|
|
FILE *f;
|
|
int err;
|
|
|
|
err = asprintf(&comm, "/proc/%d/comm", pid);
|
|
if (err < 0)
|
|
return NULL;
|
|
|
|
f = fopen(comm, "r");
|
|
free(comm);
|
|
if (!f) {
|
|
perror("fopen");
|
|
return NULL;
|
|
}
|
|
|
|
if (fscanf(f, "%ms\n", &comm) != 1) {
|
|
perror("fscanf");
|
|
comm = NULL;
|
|
}
|
|
|
|
|
|
if (fclose(f))
|
|
perror("fclose");
|
|
|
|
return comm;
|
|
}
|
|
|
|
static void show_processes(const char *name)
|
|
{
|
|
glob_t globbuf = { };
|
|
char **fd_path;
|
|
int err;
|
|
|
|
err = glob("/proc/[0-9]*/fd/[0-9]*", GLOB_NOSORT,
|
|
NULL, &globbuf);
|
|
if (err)
|
|
return;
|
|
|
|
fd_path = globbuf.gl_pathv;
|
|
while (*fd_path) {
|
|
const char *dev_net_tun = "/dev/net/tun";
|
|
const size_t linkbuf_len = strlen(dev_net_tun) + 2;
|
|
char linkbuf[linkbuf_len], *fdinfo;
|
|
int pid, fd;
|
|
FILE *f;
|
|
|
|
if (sscanf(*fd_path, "/proc/%d/fd/%d", &pid, &fd) != 2)
|
|
goto next;
|
|
|
|
if (pid == getpid())
|
|
goto next;
|
|
|
|
err = readlink(*fd_path, linkbuf, linkbuf_len - 1);
|
|
if (err < 0) {
|
|
perror("readlink");
|
|
goto next;
|
|
}
|
|
linkbuf[err] = '\0';
|
|
if (strcmp(dev_net_tun, linkbuf))
|
|
goto next;
|
|
|
|
if (asprintf(&fdinfo, "/proc/%d/fdinfo/%d", pid, fd) < 0)
|
|
goto next;
|
|
|
|
f = fopen(fdinfo, "r");
|
|
free(fdinfo);
|
|
if (!f) {
|
|
perror("fopen");
|
|
goto next;
|
|
}
|
|
|
|
while (!feof(f)) {
|
|
char *key = NULL, *value = NULL;
|
|
|
|
err = fscanf(f, "%m[^:]: %ms\n", &key, &value);
|
|
if (err == EOF) {
|
|
if (ferror(f))
|
|
perror("fscanf");
|
|
break;
|
|
} else if (err == 2 &&
|
|
!strcmp("iff", key) &&
|
|
!strcmp(name, value)) {
|
|
char *pname = pid_name(pid);
|
|
|
|
printf(" %s(%d)", pname ? : "<NULL>", pid);
|
|
free(pname);
|
|
}
|
|
|
|
free(key);
|
|
free(value);
|
|
}
|
|
if (fclose(f))
|
|
perror("fclose");
|
|
|
|
next:
|
|
++fd_path;
|
|
}
|
|
|
|
globfree(&globbuf);
|
|
}
|
|
|
|
|
|
static int do_show(int argc, char **argv)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *d;
|
|
long flags, owner = -1, group = -1;
|
|
|
|
dir = opendir("/sys/class/net");
|
|
if (!dir) {
|
|
perror("opendir");
|
|
return -1;
|
|
}
|
|
while ((d = readdir(dir))) {
|
|
if (d->d_name[0] == '.' &&
|
|
(d->d_name[1] == 0 || d->d_name[1] == '.'))
|
|
continue;
|
|
|
|
if (read_prop(d->d_name, "tun_flags", &flags))
|
|
continue;
|
|
|
|
read_prop(d->d_name, "owner", &owner);
|
|
read_prop(d->d_name, "group", &group);
|
|
|
|
printf("%s:", d->d_name);
|
|
print_flags(flags);
|
|
if (owner != -1)
|
|
printf(" user %ld", owner);
|
|
if (group != -1)
|
|
printf(" group %ld", group);
|
|
printf("\n");
|
|
if (show_details) {
|
|
printf("\tAttached to processes:");
|
|
show_processes(d->d_name);
|
|
printf("\n");
|
|
}
|
|
}
|
|
closedir(dir);
|
|
return 0;
|
|
}
|
|
|
|
int do_iptuntap(int argc, char **argv)
|
|
{
|
|
if (argc > 0) {
|
|
if (matches(*argv, "add") == 0)
|
|
return do_add(argc-1, argv+1);
|
|
if (matches(*argv, "delete") == 0)
|
|
return do_del(argc-1, argv+1);
|
|
if (matches(*argv, "show") == 0 ||
|
|
matches(*argv, "lst") == 0 ||
|
|
matches(*argv, "list") == 0)
|
|
return do_show(argc-1, argv+1);
|
|
if (matches(*argv, "help") == 0)
|
|
usage();
|
|
} else
|
|
return do_show(0, NULL);
|
|
|
|
fprintf(stderr, "Command \"%s\" is unknown, try \"ip tuntap help\".\n",
|
|
*argv);
|
|
exit(-1);
|
|
}
|