/* * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rt_names.h" #include "utils.h" #include "ip_common.h" static const char drv_name[] = "tun"; #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" " [ mode { tun | tap } ] [ user USER ] [ group GROUP ]\n" " [ one_queue ] [ pi ] [ vnet_hdr ] [ multi_queue ] [ name NAME ]\n" "\n" "Where: USER := { STRING | NUMBER }\n" " 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 void print_flags(long flags) { open_json_array(PRINT_JSON, "flags"); if (flags & IFF_TUN) print_string(PRINT_ANY, NULL, " %s", "tun"); if (flags & IFF_TAP) print_string(PRINT_ANY, NULL, " %s", "tap"); if (!(flags & IFF_NO_PI)) print_string(PRINT_ANY, NULL, " %s", "pi"); if (flags & IFF_ONE_QUEUE) print_string(PRINT_ANY, NULL, " %s", "one_queue"); if (flags & IFF_VNET_HDR) print_string(PRINT_ANY, NULL, " %s", "vnet_hdr"); if (flags & IFF_PERSIST) print_string(PRINT_ANY, NULL, " %s", "persist"); if (!(flags & IFF_NOFILTER)) print_string(PRINT_ANY, NULL, " %s", "filter"); flags &= ~(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | IFF_PERSIST | IFF_NOFILTER); if (flags) print_0xhex(PRINT_ANY, NULL, "%#llx", flags); close_json_array(PRINT_JSON, NULL); } 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; open_json_array(PRINT_JSON, "processes"); 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); print_string(PRINT_ANY, "name", "%s", pname ? : ""); print_uint(PRINT_ANY, "pid", "(%d)", pid); free(pname); } free(key); free(value); } if (fclose(f)) perror("fclose"); next: ++fd_path; } close_json_array(PRINT_JSON, NULL); globfree(&globbuf); } static int tuntap_filter_req(struct nlmsghdr *nlh, int reqlen) { struct rtattr *linkinfo; int err; linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO); err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, drv_name, sizeof(drv_name) - 1); if (err) return err; addattr_nest_end(nlh, linkinfo); return 0; } static int print_tuntap(struct nlmsghdr *n, void *arg) { struct ifinfomsg *ifi = NLMSG_DATA(n); struct rtattr *tb[IFLA_MAX+1]; struct rtattr *linkinfo[IFLA_INFO_MAX+1]; const char *name, *kind; long flags, owner = -1, group = -1; if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) return 0; if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi))) return -1; switch (ifi->ifi_type) { case ARPHRD_NONE: case ARPHRD_ETHER: break; default: return 0; } parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n)); if (!tb[IFLA_IFNAME]) return 0; if (!tb[IFLA_LINKINFO]) return 0; parse_rtattr_nested(linkinfo, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); if (!linkinfo[IFLA_INFO_KIND]) return 0; kind = rta_getattr_str(linkinfo[IFLA_INFO_KIND]); if (strcmp(kind, drv_name)) return 0; name = rta_getattr_str(tb[IFLA_IFNAME]); if (read_prop(name, "tun_flags", &flags)) return 0; if (read_prop(name, "owner", &owner)) return 0; if (read_prop(name, "group", &group)) return 0; open_json_object(NULL); print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname", "%s:", name); print_flags(flags); if (owner != -1) print_u64(PRINT_ANY, "user", " user %ld", owner); if (group != -1) print_u64(PRINT_ANY, "group", " group %ld", group); if (show_details) { print_string(PRINT_FP, NULL, "%s\tAttached to processes:", _SL_); show_processes(name); } close_json_object(); print_string(PRINT_FP, NULL, "%s", "\n"); return 0; } static int do_show(int argc, char **argv) { if (rtnl_linkdump_req_filter_fn(&rth, AF_UNSPEC, tuntap_filter_req) < 0) { perror("Cannot send dump request\n"); return -1; } new_json_obj(json); if (rtnl_dump_filter(&rth, print_tuntap, NULL) < 0) { fprintf(stderr, "Dump terminated\n"); return -1; } delete_json_obj(); fflush(stdout); 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); } static void print_owner(FILE *f, uid_t uid) { struct passwd *pw = getpwuid(uid); if (pw) print_string(PRINT_ANY, "user", "user %s ", pw->pw_name); else print_uint(PRINT_ANY, "user", "user %u ", uid); } static void print_group(FILE *f, gid_t gid) { struct group *group = getgrgid(gid); if (group) print_string(PRINT_ANY, "group", "group %s ", group->gr_name); else print_uint(PRINT_ANY, "group", "group %u ", gid); } static void print_mq(FILE *f, struct rtattr *tb[]) { if (!tb[IFLA_TUN_MULTI_QUEUE] || !rta_getattr_u8(tb[IFLA_TUN_MULTI_QUEUE])) { if (is_json_context()) print_bool(PRINT_JSON, "multi_queue", NULL, false); return; } print_bool(PRINT_ANY, "multi_queue", "multi_queue ", true); if (tb[IFLA_TUN_NUM_QUEUES]) { print_uint(PRINT_ANY, "numqueues", "numqueues %u ", rta_getattr_u32(tb[IFLA_TUN_NUM_QUEUES])); } if (tb[IFLA_TUN_NUM_DISABLED_QUEUES]) { print_uint(PRINT_ANY, "numdisabled", "numdisabled %u ", rta_getattr_u32(tb[IFLA_TUN_NUM_DISABLED_QUEUES])); } } static void print_onoff(FILE *f, const char *flag, __u8 val) { if (is_json_context()) print_bool(PRINT_JSON, flag, NULL, !!val); else fprintf(f, "%s %s ", flag, val ? "on" : "off"); } static void print_type(FILE *f, __u8 type) { SPRINT_BUF(buf); const char *str = buf; if (type == IFF_TUN) str = "tun"; else if (type == IFF_TAP) str = "tap"; else snprintf(buf, sizeof(buf), "UNKNOWN:%hhu", type); print_string(PRINT_ANY, "type", "type %s ", str); } static void tun_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[]) { if (!tb) return; if (tb[IFLA_TUN_TYPE]) print_type(f, rta_getattr_u8(tb[IFLA_TUN_TYPE])); if (tb[IFLA_TUN_PI]) print_onoff(f, "pi", rta_getattr_u8(tb[IFLA_TUN_PI])); if (tb[IFLA_TUN_VNET_HDR]) { print_onoff(f, "vnet_hdr", rta_getattr_u8(tb[IFLA_TUN_VNET_HDR])); } print_mq(f, tb); if (tb[IFLA_TUN_PERSIST]) print_onoff(f, "persist", rta_getattr_u8(tb[IFLA_TUN_PERSIST])); if (tb[IFLA_TUN_OWNER]) print_owner(f, rta_getattr_u32(tb[IFLA_TUN_OWNER])); if (tb[IFLA_TUN_GROUP]) print_group(f, rta_getattr_u32(tb[IFLA_TUN_GROUP])); } struct link_util tun_link_util = { .id = "tun", .maxattr = IFLA_TUN_MAX, .print_opt = tun_print_opt, };