From 46afa6947b4ab89c306703386bbf3379ad464a1f Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 16 Feb 2017 08:58:55 -0800 Subject: [PATCH 1/4] ip vrf: Handle vrf in a cgroup hierarchy Add support for VRF in a pre-existing hierarchy. For example, if the current process is running in CGRP/foo/bar, the 'ip vrf exec NAME CMD' should run CMD in the cgroup CGRP/foo/bar/vrf/NAME. When listing process ids in a VRF, search for the directory vrf/NAME regardless of base path (foo/bar/vrf/NAME and vrf/NAME) are still running against the same vrf NAME. Reported-by: Andy Lutomirski Signed-off-by: David Ahern --- ip/ipvrf.c | 173 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 28 deletions(-) diff --git a/ip/ipvrf.c b/ip/ipvrf.c index 8bd99d62..8d61d071 100644 --- a/ip/ipvrf.c +++ b/ip/ipvrf.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,10 @@ static void usage(void) exit(-1); } +/* + * parse process based cgroup file looking for PATH/vrf/NAME where + * NAME is the name of the vrf the process is associated with + */ static int vrf_identify(pid_t pid, char *name, size_t len) { char path[PATH_MAX]; @@ -55,9 +60,13 @@ static int vrf_identify(pid_t pid, char *name, size_t len) memset(name, 0, len); while (fgets(buf, sizeof(buf), fp)) { - vrf = strstr(buf, "::/vrf/"); + /* want the controller-less cgroup */ + if (strstr(buf, "::/") == NULL) + continue; + + vrf = strstr(buf, "/vrf/"); if (vrf) { - vrf += 7; /* skip past "::/vrf/" */ + vrf += 5; /* skip past "/vrf/" */ end = strchr(vrf, '\n'); if (end) *end = '\0'; @@ -97,13 +106,82 @@ static int ipvrf_identify(int argc, char **argv) return rc; } -static int ipvrf_pids(int argc, char **argv) +/* read PATH/vrf/NAME/cgroup.procs file */ +static void read_cgroup_pids(const char *base_path, char *name) { char path[PATH_MAX]; char buf[4096]; - char *mnt, *vrf; - int fd, rc = -1; ssize_t n; + int fd; + + if (snprintf(path, sizeof(path), "%s/vrf/%s%s", + base_path, name, CGRP_PROC_FILE) >= sizeof(path)) + return; + + fd = open(path, O_RDONLY); + if (fd < 0) + return; /* no cgroup file, nothing to show */ + + /* dump contents (pids) of cgroup.procs */ + while (1) { + n = read(fd, buf, sizeof(buf) - 1); + if (n <= 0) + break; + + printf("%s", buf); + } + + close(fd); +} + +/* recurse path looking for PATH/vrf/NAME */ +static int recurse_dir(char *base_path, char *name) +{ + char path[PATH_MAX]; + struct dirent *de; + struct stat fstat; + int rc; + DIR *d; + + d = opendir(base_path); + if (!d) + return -1; + + while ((de = readdir(d)) != NULL) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + if (!strcmp(de->d_name, "vrf")) { + read_cgroup_pids(base_path, name); + continue; + } + + /* is this a subdir that needs to be walked */ + if (snprintf(path, sizeof(path), "%s/%s", + base_path, de->d_name) >= sizeof(path)) + continue; + + if (lstat(path, &fstat) < 0) + continue; + + if (S_ISDIR(fstat.st_mode)) { + rc = recurse_dir(path, name); + if (rc != 0) + goto out; + } + } + + rc = 0; +out: + closedir(d); + + return rc; +} + +static int ipvrf_pids(int argc, char **argv) +{ + char *mnt, *vrf; + int ret; if (argc != 1) { fprintf(stderr, "Invalid arguments\n"); @@ -116,29 +194,11 @@ static int ipvrf_pids(int argc, char **argv) if (!mnt) return -1; - snprintf(path, sizeof(path), "%s/vrf/%s%s", mnt, vrf, CGRP_PROC_FILE); + ret = recurse_dir(mnt, vrf); + free(mnt); - fd = open(path, O_RDONLY); - if (fd < 0) - return 0; /* no cgroup file, nothing to show */ - while (1) { - n = read(fd, buf, sizeof(buf) - 1); - if (n < 0) { - fprintf(stderr, - "Failed to read cgroups file: %s\n", - strerror(errno)); - break; - } else if (n == 0) { - rc = 0; - break; - } - printf("%s", buf); - } - - close(fd); - - return rc; + return ret; } /* load BPF program to set sk_bound_dev_if for sockets */ @@ -203,9 +263,60 @@ out: return rc; } +/* get base path for controller-less cgroup for a process. + * path returned does not include /vrf/NAME if it exists + */ +static int vrf_path(char *vpath, size_t len) +{ + char path[PATH_MAX]; + char buf[4096]; + char *vrf; + FILE *fp; + + snprintf(path, sizeof(path), "/proc/%d/cgroup", getpid()); + fp = fopen(path, "r"); + if (!fp) + return -1; + + vpath[0] = '\0'; + + while (fgets(buf, sizeof(buf), fp)) { + char *start, *nl; + + start = strstr(buf, "::/"); + if (!start) + continue; + + /* advance past '::' */ + start += 2; + + nl = strchr(start, '\n'); + if (nl) + *nl = '\0'; + + vrf = strstr(start, "/vrf"); + if (vrf) + *vrf = '\0'; + + strncpy(vpath, start, len - 1); + vpath[len - 1] = '\0'; + + /* if vrf path is just / then return nothing */ + if (!strcmp(vpath, "/")) + vpath[0] = '\0'; + + break; + } + + fclose(fp); + + return 0; +} + static int vrf_switch(const char *name) { char path[PATH_MAX], *mnt, pid[16]; + char vpath[PATH_MAX]; int ifindex = 0; int rc = -1, len, fd = -1; @@ -221,11 +332,17 @@ static int vrf_switch(const char *name) if (!mnt) return -1; + if (vrf_path(vpath, sizeof(vpath)) < 0) { + fprintf(stderr, "Failed to get base cgroup path: %s\n", + strerror(errno)); + return -1; + } + /* path to cgroup; make sure buffer has room to cat "/cgroup.procs" * to the end of the path */ - len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE), "%s/vrf/%s", - mnt, ifindex ? name : ""); + len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE), + "%s%s/vrf/%s", mnt, vpath, ifindex ? name : ""); if (len > sizeof(path) - sizeof(CGRP_PROC_FILE)) { fprintf(stderr, "Invalid path to cgroup2 mount\n"); goto out; From 9c49438a6716c7ac5fc2d7ff757bbe6cd9805ba8 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 16 Feb 2017 08:58:56 -0800 Subject: [PATCH 2/4] ip netns: refactor netns_identify Move guts of netns_identify into a standalone function that returns the netns name in a given buffer. Signed-off-by: David Ahern --- ip/ip_common.h | 1 + ip/ipnetns.c | 47 +++++++++++++++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/ip/ip_common.h b/ip/ip_common.h index ab6a8343..e8642a18 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -59,6 +59,7 @@ int do_ipnetconf(int argc, char **argv); int do_iptoken(int argc, char **argv); int do_ipvrf(int argc, char **argv); void vrf_reset(void); +int netns_identify_pid(const char *pidstr, char *name, int len); int iplink_get(unsigned int flags, char *name, __u32 filt_mask); diff --git a/ip/ipnetns.c b/ip/ipnetns.c index 8201b94a..0b0378ab 100644 --- a/ip/ipnetns.c +++ b/ip/ipnetns.c @@ -468,28 +468,15 @@ static int netns_pids(int argc, char **argv) } -static int netns_identify(int argc, char **argv) +int netns_identify_pid(const char *pidstr, char *name, int len) { - const char *pidstr; char net_path[PATH_MAX]; int netns; struct stat netst; DIR *dir; struct dirent *entry; - if (argc < 1) { - pidstr = "self"; - } else if (argc > 1) { - fprintf(stderr, "extra arguments specified\n"); - return -1; - } else { - pidstr = argv[0]; - if (!is_pid(pidstr)) { - fprintf(stderr, "Specified string '%s' is not a pid\n", - pidstr); - return -1; - } - } + name[0] = '\0'; snprintf(net_path, sizeof(net_path), "/proc/%s/ns/net", pidstr); netns = open(net_path, O_RDONLY); @@ -531,7 +518,8 @@ static int netns_identify(int argc, char **argv) if ((st.st_dev == netst.st_dev) && (st.st_ino == netst.st_ino)) { - printf("%s\n", entry->d_name); + strncpy(name, entry->d_name, len - 1); + name[len - 1] = '\0'; } } closedir(dir); @@ -539,6 +527,33 @@ static int netns_identify(int argc, char **argv) } +static int netns_identify(int argc, char **argv) +{ + const char *pidstr; + char name[256]; + int rc; + + if (argc < 1) { + pidstr = "self"; + } else if (argc > 1) { + fprintf(stderr, "extra arguments specified\n"); + return -1; + } else { + pidstr = argv[0]; + if (!is_pid(pidstr)) { + fprintf(stderr, "Specified string '%s' is not a pid\n", + pidstr); + return -1; + } + } + + rc = netns_identify_pid(pidstr, name, sizeof(name)); + if (!rc) + printf("%s\n", name); + + return rc; +} + static int on_netns_del(char *nsname, void *arg) { char netns_path[PATH_MAX]; From 6a9783831c081c1b759ff3bcd704c37bcc3775de Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 16 Feb 2017 08:58:57 -0800 Subject: [PATCH 3/4] ip vrf: Handle VRF nesting in namespace Since cgroups are not namespace aware, the directory heirarchy used by ip vrf should account for network namespaces. In this case, change the path from CGRP/BASE/vrf/NAME to CGRP/BASE/NETNS/vrf/NAME where CGRP is the cgroup2 mount path, BASE in any base heirarchy inherited before VRF is applied and NAME is the VRF name. The intent is as follows: a user logs into the box into some namespace with a name known to iproute2. Some other policy may have put the process into a BASE heirarchy. From there the user executes a task in a VRF and in doing so the task heirarchy becomes CGRP/BASE/NETNS/vrf/NAME. The namespace level is omitted for the default namespace. Reported-by: Andy Lutomirski Signed-off-by: David Ahern --- ip/ipvrf.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/ip/ipvrf.c b/ip/ipvrf.c index 8d61d071..cb7f9fa6 100644 --- a/ip/ipvrf.c +++ b/ip/ipvrf.c @@ -134,8 +134,8 @@ static void read_cgroup_pids(const char *base_path, char *name) close(fd); } -/* recurse path looking for PATH/vrf/NAME */ -static int recurse_dir(char *base_path, char *name) +/* recurse path looking for PATH[/NETNS]/vrf/NAME */ +static int recurse_dir(char *base_path, char *name, const char *netns) { char path[PATH_MAX]; struct dirent *de; @@ -152,7 +152,15 @@ static int recurse_dir(char *base_path, char *name) continue; if (!strcmp(de->d_name, "vrf")) { - read_cgroup_pids(base_path, name); + const char *pdir = strrchr(base_path, '/'); + + /* found a 'vrf' directory. if it is for the given + * namespace then dump the cgroup pids + */ + if (*netns == '\0' || + (pdir && !strcmp(pdir+1, netns))) + read_cgroup_pids(base_path, name); + continue; } @@ -165,7 +173,7 @@ static int recurse_dir(char *base_path, char *name) continue; if (S_ISDIR(fstat.st_mode)) { - rc = recurse_dir(path, name); + rc = recurse_dir(path, name, netns); if (rc != 0) goto out; } @@ -178,10 +186,25 @@ out: return rc; } +static int ipvrf_get_netns(char *netns, int len) +{ + if (netns_identify_pid("self", netns, len-3)) { + fprintf(stderr, "Failed to get name of network namespace: %s\n", + strerror(errno)); + return -1; + } + + if (*netns != '\0') + strcat(netns, "-ns"); + + return 0; +} + static int ipvrf_pids(int argc, char **argv) { char *mnt, *vrf; - int ret; + char netns[256]; + int ret = -1; if (argc != 1) { fprintf(stderr, "Invalid arguments\n"); @@ -194,8 +217,12 @@ static int ipvrf_pids(int argc, char **argv) if (!mnt) return -1; - ret = recurse_dir(mnt, vrf); + if (ipvrf_get_netns(netns, sizeof(netns)) < 0) + goto out; + ret = recurse_dir(mnt, vrf, netns); + +out: free(mnt); return ret; @@ -316,7 +343,7 @@ static int vrf_path(char *vpath, size_t len) static int vrf_switch(const char *name) { char path[PATH_MAX], *mnt, pid[16]; - char vpath[PATH_MAX]; + char vpath[PATH_MAX], netns[256]; int ifindex = 0; int rc = -1, len, fd = -1; @@ -332,17 +359,37 @@ static int vrf_switch(const char *name) if (!mnt) return -1; + /* -1 on length to add '/' to the end */ + if (ipvrf_get_netns(netns, sizeof(netns) - 1) < 0) + return -1; + if (vrf_path(vpath, sizeof(vpath)) < 0) { fprintf(stderr, "Failed to get base cgroup path: %s\n", strerror(errno)); return -1; } + /* if path already ends in netns then don't add it again */ + if (*netns != '\0') { + char *pdir = strrchr(vpath, '/'); + + if (!pdir) + pdir = vpath; + else + pdir++; + + if (strcmp(pdir, netns) == 0) + *pdir = '\0'; + + strcat(netns, "/"); + } + /* path to cgroup; make sure buffer has room to cat "/cgroup.procs" * to the end of the path */ len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE), - "%s%s/vrf/%s", mnt, vpath, ifindex ? name : ""); + "%s%s/%svrf/%s", + mnt, vpath, netns, ifindex ? name : ""); if (len > sizeof(path) - sizeof(CGRP_PROC_FILE)) { fprintf(stderr, "Invalid path to cgroup2 mount\n"); goto out; From b5377431df6de037b668eba418dca28f1113d303 Mon Sep 17 00:00:00 2001 From: David Ahern Date: Thu, 16 Feb 2017 08:58:58 -0800 Subject: [PATCH 4/4] ip vrf: Detect invalid vrf name in pids command Verify VRF name is valid before attempting to read cgroups files. Signed-off-by: David Ahern --- ip/ipvrf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ip/ipvrf.c b/ip/ipvrf.c index cb7f9fa6..5e204a9e 100644 --- a/ip/ipvrf.c +++ b/ip/ipvrf.c @@ -212,6 +212,10 @@ static int ipvrf_pids(int argc, char **argv) } vrf = argv[0]; + if (!name_is_vrf(vrf)) { + fprintf(stderr, "Invalid VRF name\n"); + return -1; + } mnt = find_cgroup2_mount(); if (!mnt)