aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <screamingmoron@gmail.com>2015-08-08 20:48:18 -0400
committerAustin Adams <screamingmoron@gmail.com>2015-08-08 20:48:18 -0400
commit5cda9dfa4bc94ab06e0da8186104119ac0006349 (patch)
tree4a65d360b50971ca5d4eea68182aea1e9d582c61
downloadnsdo-5cda9dfa4bc94ab06e0da8186104119ac0006349.tar.gz
nsdo-5cda9dfa4bc94ab06e0da8186104119ac0006349.tar.xz
initial commit
-rw-r--r--.gitignore3
-rw-r--r--LICENSE20
-rw-r--r--Makefile29
-rw-r--r--README.md72
-rw-r--r--nsdo.115
-rw-r--r--nsdo.c204
-rw-r--r--readme.head43
7 files changed, 386 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ff5d630
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+nsdo
+nsdo.1.gz
+.*.sw?
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8470fc6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Austin Adams
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..390723a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
+CFLAGS ?= -Wall -Werror -O3
+PREFIX ?= /usr/local
+
+PROG = nsdo
+MANSECTION = 1
+MANPAGE = $(PROG).$(MANSECTION)
+MANPAGEGZ = $(MANPAGE).gz
+README = README.md
+
+.PHONY: all install clean
+
+all: $(PROG) $(MANPAGEGZ) $(README)
+
+install: $(PROG) $(MANPAGEGZ)
+ install -Dm6755 $< $(PREFIX)/bin/$<
+ install -Dm644 $(word 2,$^) $(PREFIX)/share/man/man$(MANSECTION)/$(word 2,$^)
+
+clean:
+ rm -fv $(PROG) $(MANPAGEGZ) $(README)
+
+$(MANPAGEGZ): $(MANPAGE)
+ gzip --best -k $<
+
+$(README): readme.head $(MANPAGE)
+ { \
+ cat $<; \
+ echo; \
+ MANWIDTH=68 man -l $(word 2,$^) | sed 's/^/ /'; \
+ } >$@
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..99e3b98
--- /dev/null
+++ b/README.md
@@ -0,0 +1,72 @@
+nsdo
+====
+
+This is a simple C program that runs a command inside a given [Linux
+network namespace][1].
+
+Effectively, it simplifies:
+
+ $ sudo ip netns exec myns sudo -u "$USER" myprogram
+
+to
+
+ $ nsdo myns myprogram
+
+Thanks to magic of the [setuid bit][2], it initially has root
+privileges, which allows it to change its own network namespace,
+`setuid()` to the user who ran the command, and then `exec()` the
+requested command.
+
+installation
+------------
+
+If you're on Arch, you can build [my AUR package][4].
+
+Otherwise:
+
+ $ make
+ # make install
+
+To change the default installation directory of `/usr/local`, set
+`PREFIX` to something else when you call `make install`.
+
+license
+-------
+[MIT/X11][3].
+
+[1]: https://lwn.net/Articles/580893/
+[2]: https://en.wikipedia.org/wiki/Setuid
+[3]: https://github.com/ausbin/nsdo/blob/master/LICENSE
+[4]: https://aur.archlinux.org/packages/nsdo-git/
+
+manpage
+-------
+
+ nsdo(1) General Commands Manual nsdo(1)
+
+
+
+ NAME
+ nsdo - run a command in a network namespace
+
+ SYNOPSIS
+ nsdo namespace command [args ...]
+
+ DESCRIPTION
+ Execute command as the current user/group in namespace, a
+ Linux network namespace setup with iproute2 (see ip-netns
+ (8)).
+
+ By default, iproute2 places network namespaces in
+ /var/run/netns/, so nsdo searces for namespaces there
+ (including namespace). To prevent command from easily
+ escaping the namespace 'jail,' nsdo will exit if the cur‐
+ rent namespace exists in that directory. Consequently, you
+ can not nest instances of nsdo.
+
+ SEE ALSO
+ ip(8), ip-netns(8), namespaces(7)
+
+
+
+ 2015-08-08 nsdo(1)
diff --git a/nsdo.1 b/nsdo.1
new file mode 100644
index 0000000..c4e2960
--- /dev/null
+++ b/nsdo.1
@@ -0,0 +1,15 @@
+.TH nsdo 1 2015-08-08
+.SH NAME
+nsdo \- run a command in a network namespace
+.SH SYNOPSIS
+.B nsdo
+.I namespace command
+[\fIargs\fR ...]
+.SH DESCRIPTION
+Execute \fIcommand\fR as the current user/group in \fInamespace\fR, a Linux network namespace setup with \fBiproute2\fR (see \fBip-netns (8)\fR).
+.PP
+By default, \fBiproute2\fR places network namespaces in /var/run/netns/, so \fBnsdo\fR searces for namespaces there (including \fInamespace\fR).
+To prevent \fIcommand\fR from easily escaping the namespace 'jail,' \fBnsdo\fR will exit if the current namespace exists in that directory.
+Consequently, you can not nest instances of \fBnsdo\fR.
+.SH SEE ALSO
+.B ip(8), ip-netns(8), namespaces(7)
diff --git a/nsdo.c b/nsdo.c
new file mode 100644
index 0000000..8643c19
--- /dev/null
+++ b/nsdo.c
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2015 Austin Adams
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define NS_PATH "/var/run/netns"
+#define PROGRAM "nsdo"
+
+enum {
+ EXIT_BAD_INVOCATION = 1,
+ EXIT_ALREADY_IN_NAMESPACE,
+ EXIT_COULDNT_SETGUID,
+ EXIT_BAD_NETNS,
+ EXIT_FAILED_EXEC
+};
+
+enum {
+ ARG_PROGRAM,
+ ARG_NETNS,
+ ARG_CMD,
+ ARG_MIN_ARGS
+};
+
+void print_usage() {
+ fprintf(stderr, "usage: " PROGRAM " <namespace> <command> [args...]\n");
+}
+
+int current_ns_inode(ino_t *inode) {
+ struct stat nsstat;
+
+ if (stat("/proc/self/ns/net", &nsstat) == -1) {
+ perror(PROGRAM ": stat(\"/proc/sys/ns/net\")");
+ return 0;
+ }
+
+ *inode = nsstat.st_ino;
+ return 1;
+}
+
+/* return values:
+ 0: inode is not a namespace in the nspath
+ 1: it is
+ -1: error encountered
+ */
+int inode_in_nspath(ino_t inode) {
+ DIR *nsdir;
+ char *nspath;
+ struct dirent *ns;
+ struct stat nsstat;
+
+ if ((nsdir = opendir(NS_PATH)) == NULL) {
+ perror(PROGRAM ": opendir(\"" NS_PATH "\")");
+ return -1;
+ }
+
+ while ((ns = readdir(nsdir)) != NULL) {
+ if (strcmp(".", ns->d_name) == 0 || strcmp("..", ns->d_name) == 0)
+ continue;
+
+ if (asprintf(&nspath, "%s/%s", NS_PATH, ns->d_name) == -1) {
+ perror(PROGRAM ": asprintf");
+ return -1;
+ }
+
+ if (stat(nspath, &nsstat) == -1) {
+ perror(PROGRAM ": stat");
+ return -1;
+ }
+
+ free(nspath);
+
+ if (nsstat.st_ino == inode)
+ return 1;
+ }
+
+ if (errno != 0) {
+ perror(PROGRAM ": readdir");
+ return -1;
+ }
+
+ closedir(nsdir);
+
+ return 0;
+}
+
+int already_in_namespace() {
+ int status;
+ ino_t inode;
+
+ if (!current_ns_inode(&inode))
+ return 1;
+
+ if ((status = inode_in_nspath(inode)) == 0) {
+ return 0;
+ } else {
+ if (status == 1)
+ fprintf(stderr, PROGRAM ": oops! i can run only in network namespaces not found in " NS_PATH "\n");
+
+ return 1;
+ }
+}
+
+int bad_nsname(char *ns) {
+ return strcmp("..", ns) == 0 || strcmp(".", ns) == 0 || strchr(ns, '/') != NULL;
+}
+
+int set_netns(char *ns) {
+ int nsfd;
+ char *nspath;
+
+ if (bad_nsname(ns)) {
+ fprintf(stderr, PROGRAM ": namespace names may not contain '/' or be '.', '..'\n");
+ return 0;
+ }
+
+ if (asprintf(&nspath, "%s/%s", NS_PATH, ns) == -1) {
+ perror(PROGRAM ": asprintf");
+ return 0;
+ }
+
+ if ((nsfd = open(nspath, O_RDONLY)) == -1) {
+ perror(PROGRAM ": open");
+ return 0;
+ }
+
+ free(nspath);
+
+ if (setns(nsfd, CLONE_NEWNET) == -1) {
+ perror(PROGRAM ": setns");
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+/* set euid+egid to the real uid+gid */
+int deescalate() {
+ if (setuid(getuid()) == -1 || setgid(getgid()) == -1) {
+ perror(PROGRAM ": set[gu]id");
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+int run(char *cmd, char **argv) {
+ if (execvp(cmd, argv) == -1) {
+ perror(PROGRAM ": execvp");
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+int main(int argc, char **argv) {
+ if (argc < ARG_MIN_ARGS) {
+ print_usage();
+ return EXIT_BAD_INVOCATION;
+ }
+
+ if (already_in_namespace())
+ return EXIT_ALREADY_IN_NAMESPACE;
+
+ if (!set_netns(argv[ARG_NETNS]))
+ return EXIT_BAD_NETNS;
+
+ if (!deescalate())
+ return EXIT_COULDNT_SETGUID;
+
+ if (!run(argv[ARG_CMD], argv+ARG_CMD))
+ return EXIT_FAILED_EXEC;
+}
diff --git a/readme.head b/readme.head
new file mode 100644
index 0000000..06d959f
--- /dev/null
+++ b/readme.head
@@ -0,0 +1,43 @@
+nsdo
+====
+
+This is a simple C program that runs a command inside a given [Linux
+network namespace][1].
+
+Effectively, it simplifies:
+
+ $ sudo ip netns exec myns sudo -u "$USER" myprogram
+
+to
+
+ $ nsdo myns myprogram
+
+Thanks to magic of the [setuid bit][2], it initially has root
+privileges, which allows it to change its own network namespace,
+`setuid()` to the user who ran the command, and then `exec()` the
+requested command.
+
+installation
+------------
+
+If you're on Arch, you can build [my AUR package][4].
+
+Otherwise:
+
+ $ make
+ # make install
+
+To change the default installation directory of `/usr/local`, set
+`PREFIX` to something else when you call `make install`.
+
+license
+-------
+[MIT/X11][3].
+
+[1]: https://lwn.net/Articles/580893/
+[2]: https://en.wikipedia.org/wiki/Setuid
+[3]: https://github.com/ausbin/nsdo/blob/master/LICENSE
+[4]: https://aur.archlinux.org/packages/nsdo-git/
+
+manpage
+-------