github.com/thediveo/gons@v0.9.9/gonamespaces.c (about) 1 /* 2 * Initializer function to join this(!) process to specific Linux-kernel 3 * namespaces before the Go runtime spins up and blocks joining certain 4 * namespaces, especially mount namespaces due to creating multiple OS 5 * threads. 6 * 7 * Compared to libcontainer's nsenter Go package, we switch namespaces on our 8 * own process (to the extend this is possible), and we switch into existing 9 * namespaces. In contrast, libcontainer's nsenter creates new namespaces for 10 * child processes it creates, so it's a completely different usecase. 11 * 12 * The namespaces to switch into are passed to us via environment variables. 13 * They're in the form of "netns=/proc/self/ns/net". Please take note that the 14 * names of the env vars are namespace names, with "ns" appended to avoid name 15 * conflicts with common environment variable names such as "pid", et cetera. 16 * 17 * Copyright 2019 Harald Albrecht. 18 * 19 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 20 * use this file except in compliance with the License.You may obtain a copy 21 * of the License at 22 * 23 * http://www.apache.org/licenses/LICENSE-2.0 24 * 25 * Unless required by applicable law or agreed to in writing, software 26 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 27 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 28 * License for the specific language governing permissions and limitations 29 * under the License. 30 */ 31 32 /* Fun stuff... */ 33 #define _GNU_SOURCE 34 #include <sched.h> 35 #include <unistd.h> 36 #include <sys/syscall.h> 37 38 /* Booooring stuff... */ 39 #include <stdlib.h> 40 #include <stdio.h> 41 #include <string.h> 42 #include <fcntl.h> 43 #include <stdarg.h> 44 #include <errno.h> 45 #include <limits.h> 46 47 /* Describes a specific type of Linux kernel namespace supported by gons. */ 48 struct ns_t { 49 char *envvarname; /* name of env variable for this type of namespace */ 50 int nstype; /* CLONE_NEWxxx constant for this type of namespace. */ 51 char *path; /* reference to a namespace in the currently mounted filesystem */ 52 int fd; /* optional fd referencing a namespace, if path==NULL */ 53 }; 54 55 /* 56 * Defines the list of supported namespaces which can be set by gons before 57 * the Go runtime spins up. Please note that setting the PID namespace will 58 * never apply to us, but only to our children. 59 */ 60 static struct ns_t namespaces[] = { 61 { "gons_cgroup", CLONE_NEWCGROUP, NULL, -1 }, 62 { "gons_ipc", CLONE_NEWIPC, NULL, -1 }, 63 { "gons_mnt", CLONE_NEWNS, NULL, -1 }, 64 { "gons_net", CLONE_NEWNET, NULL, -1 }, 65 { "gons_pid", CLONE_NEWPID, NULL, -1 }, 66 { "gons_user", CLONE_NEWUSER, NULL, -1 }, 67 { "gons_uts", CLONE_NEWUTS, NULL, -1 } 68 }; 69 70 /* Number of namespace (types) */ 71 #define NSCOUNT (sizeof(namespaces) / sizeof(namespaces[0])) 72 73 /* Default order if no order has been given ;) */ 74 static char *defaultorder = 75 "!user,!mnt,!cgroup,!ipc,!net,!pid,!uts"; 76 77 /* 78 * If not NULL, then points to a buffer with an error message for later 79 * consumption by an application in order to detect namespace switching 80 * errors. Its size not only accounts for the maximum path size, but also for 81 * some descriptive text prefixing it. 82 */ 83 char *gonsmsg; 84 static unsigned int maxmsgsize; 85 86 /* 87 * Our last-resort error reporting, which the application should later pick up 88 * by calling the Go function gons.Status(). 89 */ 90 static void logerr(const char *format, ...) { 91 va_list args; 92 /* Create a buffer if not done so. */ 93 if (!gonsmsg) { 94 maxmsgsize = 256 + PATH_MAX; 95 gonsmsg = (char *) malloc(maxmsgsize); 96 if (!gonsmsg) { 97 /* Handle oom and protect against overwriting this message */ 98 gonsmsg = "malloc error"; 99 maxmsgsize = 0; 100 return; 101 } 102 } 103 /* Generate the error message... */ 104 va_start(args, format); 105 /* 106 * He who has never ignored printf()'s return value, cast the first stone. 107 */ 108 vsnprintf(gonsmsg, maxmsgsize, format, args); 109 va_end(args); 110 } 111 112 /* 113 * Switch into the Linux kernel namespaces specified through env variables: 114 * these env vars reference namespaces in the filesystem, such as 115 * "netns=/proc/$PID/ns/net". See the static constant "namespaces" above for 116 * the set of Linux namespaces supported. 117 */ 118 void gonamespaces(void) { 119 // Find out whether we should keep some ooooorder ;) The order describes 120 // the sequence in which the namespaces should be entered whether the 121 // paths are resolved into fds before the first setns(), or as the setns() 122 // happen. 123 int seq[NSCOUNT]; // indices into namespaces array 124 int seqlen = 0; 125 char *ooorder = getenv("gons_order"); 126 // In case no order has been given, then we will employ our default order. 127 if (ooorder == NULL || !*ooorder) ooorder = defaultorder; 128 // Remember: the environment is not ours ;) (...to write into) 129 ooorder = strdup(ooorder); 130 while (*ooorder && seqlen < NSCOUNT) { 131 int fdref = *ooorder == '!'; 132 if (fdref) ++ooorder; 133 char *delimiter = strchr(ooorder, ','); 134 if (delimiter != NULL) *delimiter++ = '\0'; 135 // Find the corresponding type element in the namespaces array by name. 136 // Please note that we skip the "gons_" prefix in the namespaces 137 // definition above. 138 int nsidx; 139 for (nsidx = 0; nsidx < NSCOUNT; ++nsidx) { 140 if (!strcmp(ooorder, namespaces[nsidx].envvarname+5)) { 141 break; 142 } 143 } 144 if (nsidx >= NSCOUNT) { 145 logerr("package gons: unknown namespace type \"%s\" in gons_order", 146 ooorder); 147 return; 148 } 149 // Get the corresponding filesystem path reference for this namespace. 150 // If not set, then skip this sequence element. 151 char *envvar = getenv(namespaces[nsidx].envvarname); 152 if (envvar && *envvar) { 153 // If the namespace should be entered using an fd-reference opened 154 // before the first setns(), then open the fd now. Otherwise just 155 // use the path later. 156 if (fdref) { 157 if (namespaces[nsidx].fd >= 0) { 158 logerr("package gons: duplicate namespace order type %s", 159 ooorder); 160 return; 161 } 162 int nsref = open(envvar, O_RDONLY); 163 if (nsref < 0) { 164 logerr("package gons: invalid %s reference \"%s\": %s", 165 namespaces[nsidx].envvarname, envvar, 166 strerror(errno)); 167 return; 168 } 169 namespaces[nsidx].fd = nsref; 170 } 171 if (namespaces[nsidx].path) { 172 logerr("package gons: duplicate namespace order type %s", 173 ooorder); 174 return; 175 } 176 namespaces[nsidx].path = envvar; 177 seq[seqlen] = nsidx; 178 ++seqlen; 179 } 180 // If we had a delimiter, then it will by now already point past it, 181 // thus to the next element in the sequence. If there wasn't a 182 // delimiter, then we simple fast forward to the \0 after the last 183 // element, so the loop will terminate. 184 if (delimiter) { 185 ooorder = delimiter; 186 } else { 187 ooorder += strlen(ooorder); 188 } 189 } 190 // Now run through the namespace switch sequence and try to let things 191 // happen... 192 for (int seqidx = 0; seqidx < seqlen; ++seqidx) { 193 int nsidx = seq[seqidx]; 194 int nsref = namespaces[nsidx].fd; 195 // If there isn't a pre-opened fd for this namespace to switch into, 196 // then we now need to open its reference. 197 if (nsref < 0) { 198 nsref = open(namespaces[nsidx].path, O_RDONLY); 199 if (nsref < 0) { 200 logerr("package gons: invalid %s reference \"%s\": %s", 201 namespaces[nsidx].envvarname, namespaces[nsidx].path, 202 strerror(errno)); 203 return; 204 } 205 } 206 /* 207 * Do not use the glibc version of setns, but go for the syscall 208 * itself. This allows us to avoid dynamically linking to glibc 209 * even when using cgo, resorting to musl, et cetera. As musl is a 210 * mixed bag in terms of its glibc compatibility, especially in 211 * such dark corners as Linux namespaces, we try to minimize 212 * problematic dependencies here. 213 * 214 * A useful reference is Dominik Honnef's blog post "Statically 215 * compiled Go programs, always, even with cgo, using musl": 216 * https://dominik.honnef.co/posts/2015/06/statically_compiled_go_programs__always__even_with_cgo__using_musl/ 217 */ 218 long res = syscall(SYS_setns, nsref, namespaces[nsidx].nstype); 219 close(nsref); /* Don't leak file descriptors */ 220 if (res < 0) { 221 logerr("package gons: cannot join %s using reference \"%s\": %s", 222 namespaces[nsidx].envvarname, namespaces[nsidx].path, 223 strerror(errno)); 224 return; 225 } 226 } 227 }