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  }