Loading src/containerctl.cpp +100 −1 Changes for src/containerctl.cpp: 100 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -35,14 +35,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * containerctl edit web env=TZ=Europe/Berlin bind=/srv/www:/usr/share/nginx/html:ro * containerctl start|stop|restart|rm web containerctl kill web [HUP] * containerctl log web [lines] containerctl jobs * containerctl shell web [command ...] a shell (or command) inside * the running container * * Settings are key=value with the keys of the API; a key given more than * once makes a list (env, arg, bind, dns, ...). */ #include <cerrno> #include <csignal> #include <cstdlib> #include <cstring> #include <fstream> #include <iostream> #include <sstream> #include <string> #include <fcntl.h> #include <sched.h> #include <sys/prctl.h> #include <sys/wait.h> #include <unistd.h> #include <vector> Loading Loading @@ -131,12 +141,100 @@ Fields settings(int argc, char **argv, int from) { return f; } // containerctl shell: into the namespaces of the container's PID 1, its // cgroup and its capability bounding set, then a shell or command as root // of the container, on the caller's terminal int shell(const std::string &name, int argc, char **argv, int from) { sysbus::Message r = call(containerd::method::GetContainer, {{"name", name}}); if (r.get("state") != "running") { std::cerr << "containerctl: " << name << " " << (lang() == "de" ? "läuft nicht" : "isn't running") << std::endl; return 1; } pid_t pid = (pid_t)atoi(r.get("pid").c_str()); std::string proc = "/proc/" + std::to_string(pid); // the capability bounding set and the cgroup of the container's PID 1 unsigned long long bnd = ~0ULL; std::string cgroup; { std::ifstream st(proc + "/status"); for (std::string l; std::getline(st, l);) if (l.compare(0, 7, "CapBnd:") == 0) bnd = strtoull(l.c_str() + 7, nullptr, 16); std::ifstream cg(proc + "/cgroup"); for (std::string l; std::getline(cg, l);) if (l.compare(0, 3, "0::") == 0) cgroup = l.substr(3); } // open all namespaces first: after entering the mount namespace, // /proc is the container's const char *kinds[] = {"cgroup", "ipc", "uts", "net", "pid", "mnt"}; int fds[6]; for (int i = 0; i < 6; ++i) if ((fds[i] = open((proc + "/ns/" + kinds[i]).c_str(), O_RDONLY | O_CLOEXEC)) < 0) { std::cerr << "containerctl: " << proc << "/ns/" << kinds[i] << ": " << strerror(errno) << std::endl; return 1; } if (!cgroup.empty()) { std::ofstream procs("/sys/fs/cgroup" + cgroup + "/cgroup.procs"); procs << getpid() << std::flush; } for (int i = 0; i < 6; ++i) { if (setns(fds[i], 0) != 0) { std::cerr << "containerctl: setns " << kinds[i] << ": " << strerror(errno) << std::endl; return 1; } close(fds[i]); } for (int c = 0; c < 64; ++c) if (!(bnd & (1ULL << c)) && prctl(PR_CAPBSET_DROP, c, 0, 0, 0) != 0 && errno == EINVAL) break; // the PID namespace takes effect for children signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); pid_t child = fork(); if (child < 0) { std::cerr << "containerctl: fork: " << strerror(errno) << std::endl; return 1; } if (child == 0) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); if (chdir("/root") != 0 && chdir("/") != 0) _exit(127); const char *term = getenv("TERM"); std::string t = std::string("TERM=") + (term && *term ? term : "xterm"); char hostname[256] = ""; gethostname(hostname, sizeof(hostname) - 1); std::string ps1 = std::string("PS1=") + hostname + ":\\w# "; const char *env[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/root", "USER=root", "LOGNAME=root", t.c_str(), ps1.c_str(), nullptr}; std::vector<char *> args; if (from < argc) for (int i = from; i < argc; ++i) args.push_back(argv[i]); else args.push_back((char *)"/bin/sh"); args.push_back(nullptr); // searched in the container's PATH (execvpe() searches the // caller's: set it first) setenv("PATH", env[0] + 5, 1); execvpe(args[0], args.data(), (char **)env); if (from >= argc) { char *bash[] = {(char *)"bash", nullptr}; execvpe("bash", bash, (char **)env); } std::cerr << "containerctl: " << args[0] << ": " << strerror(errno) << std::endl; _exit(127); } int status = 0; while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); } int usage() { std::cerr << "usage: containerctl images | pull IMAGE | rmi IMAGE\n" " containerctl list | status NAME | log NAME [LINES] | jobs\n" " containerctl create NAME image=IMAGE [key=value ...]\n" " containerctl edit NAME key=value ...\n" " containerctl start|stop|restart|rm NAME | kill NAME [SIGNAL]\n"; " containerctl start|stop|restart|rm NAME | kill NAME [SIGNAL]\n" " containerctl shell NAME [COMMAND ...]\n"; return 2; } Loading Loading @@ -216,5 +314,6 @@ int main(int argc, char **argv) { return 0; } if (cmd == "rm") return follow(call(m::DeleteContainer, {{"name", arg}})); if (cmd == "shell") return shell(arg, argc, argv, 3); return usage(); } src/containers.cpp +4 −1 Changes for src/containers.cpp: 4 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -291,9 +291,12 @@ Result startOne(const std::string &name) { if (!isDir(layerDir(layer))) return fail(tr("a layer of %1 is missing; pull it again", "eine Schicht von %1 fehlt; bitte neu laden", {s.image}), 500); if (s.network == "bridge" && !isDir("/sys/class/net/" + s.bridge + "/bridge")) return fail(tr("there is no bridge %1", "es gibt keine Bridge %1", {s.bridge}), 409); // a missing bind mount source is created as a directory (a file to // mount has to exist) for (auto &b : s.binds) { std::string src = b.substr(0, b.find(':')); if (!exists(src)) return fail(tr("the bind mount source %1 doesn't exist", "die Quelle %1 des Bind-Mounts gibt es nicht", {src}), 409); if (!exists(src) && !mkdirs(src, 0755)) return fail(sysErr(tr("creating the bind mount source %1", "Anlegen der Bind-Mount-Quelle %1", {src}), errno), 500); } for (auto &d : s.devices) { std::string src = d.substr(0, d.find(':')); Loading src/devices.cpp +7 −1 Changes for src/devices.cpp: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -100,10 +100,16 @@ std::string attachDeviceFilter(const std::string &cgroup, const std::vector<Devi load.insns = (uint64_t)(uintptr_t)p.data(); load.insn_cnt = (uint32_t)p.size(); load.license = (uint64_t)(uintptr_t) "GPL"; // without a verifier log first: a log longer than the buffer (a long // device list) fails the load with ENOSPC on its own int prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); if (prog < 0) { // again with the log, for the reason load.log_buf = (uint64_t)(uintptr_t)log; load.log_size = sizeof(log); load.log_level = 1; int prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); } if (prog < 0) { int e = errno; std::string why = sysErr("bpf(BPF_PROG_LOAD)", e); Loading src/netlink.cpp +7 −2 Changes for src/netlink.cpp: 7 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -136,17 +136,22 @@ bool parseMac(const std::string &s, uint8_t out[6]) { } // namespace std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac) { std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac, const std::string &hostMac) { Msg m(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL); m.fixed<ifinfomsg>()->ifi_family = AF_UNSPEC; m.str(IFLA_IFNAME, name); uint8_t mac[6]; if (!hostMac.empty()) { if (!parseMac(hostMac, mac)) return "invalid MAC address " + hostMac; m.attr(IFLA_ADDRESS, mac, 6); } size_t info = m.begin(IFLA_LINKINFO); m.str(IFLA_INFO_KIND, "veth"); size_t data = m.begin(IFLA_INFO_DATA); size_t p = m.begin(VETH_INFO_PEER); m.fixed<ifinfomsg>()->ifi_family = AF_UNSPEC; m.str(IFLA_IFNAME, peer); uint8_t mac[6]; if (!peerMac.empty()) { if (!parseMac(peerMac, mac)) return "invalid MAC address " + peerMac; m.attr(IFLA_ADDRESS, mac, 6); Loading src/netlink.h +4 −2 Changes for src/netlink.h: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -38,8 +38,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace containerd::nl { // peerMac: "02:.." for the peer (the container's end), "" for a random one std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac = ""); // peerMac: "02:.." for the peer (the container's end), hostMac for name // (the host's end); "" for a random one std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac = "", const std::string &hostMac = ""); std::string setMaster(const std::string &ifname, const std::string &bridge); std::string setUp(const std::string &ifname); std::string moveToNetns(const std::string &ifname, pid_t pid); Loading Loading
src/containerctl.cpp +100 −1 Changes for src/containerctl.cpp: 100 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -35,14 +35,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * containerctl edit web env=TZ=Europe/Berlin bind=/srv/www:/usr/share/nginx/html:ro * containerctl start|stop|restart|rm web containerctl kill web [HUP] * containerctl log web [lines] containerctl jobs * containerctl shell web [command ...] a shell (or command) inside * the running container * * Settings are key=value with the keys of the API; a key given more than * once makes a list (env, arg, bind, dns, ...). */ #include <cerrno> #include <csignal> #include <cstdlib> #include <cstring> #include <fstream> #include <iostream> #include <sstream> #include <string> #include <fcntl.h> #include <sched.h> #include <sys/prctl.h> #include <sys/wait.h> #include <unistd.h> #include <vector> Loading Loading @@ -131,12 +141,100 @@ Fields settings(int argc, char **argv, int from) { return f; } // containerctl shell: into the namespaces of the container's PID 1, its // cgroup and its capability bounding set, then a shell or command as root // of the container, on the caller's terminal int shell(const std::string &name, int argc, char **argv, int from) { sysbus::Message r = call(containerd::method::GetContainer, {{"name", name}}); if (r.get("state") != "running") { std::cerr << "containerctl: " << name << " " << (lang() == "de" ? "läuft nicht" : "isn't running") << std::endl; return 1; } pid_t pid = (pid_t)atoi(r.get("pid").c_str()); std::string proc = "/proc/" + std::to_string(pid); // the capability bounding set and the cgroup of the container's PID 1 unsigned long long bnd = ~0ULL; std::string cgroup; { std::ifstream st(proc + "/status"); for (std::string l; std::getline(st, l);) if (l.compare(0, 7, "CapBnd:") == 0) bnd = strtoull(l.c_str() + 7, nullptr, 16); std::ifstream cg(proc + "/cgroup"); for (std::string l; std::getline(cg, l);) if (l.compare(0, 3, "0::") == 0) cgroup = l.substr(3); } // open all namespaces first: after entering the mount namespace, // /proc is the container's const char *kinds[] = {"cgroup", "ipc", "uts", "net", "pid", "mnt"}; int fds[6]; for (int i = 0; i < 6; ++i) if ((fds[i] = open((proc + "/ns/" + kinds[i]).c_str(), O_RDONLY | O_CLOEXEC)) < 0) { std::cerr << "containerctl: " << proc << "/ns/" << kinds[i] << ": " << strerror(errno) << std::endl; return 1; } if (!cgroup.empty()) { std::ofstream procs("/sys/fs/cgroup" + cgroup + "/cgroup.procs"); procs << getpid() << std::flush; } for (int i = 0; i < 6; ++i) { if (setns(fds[i], 0) != 0) { std::cerr << "containerctl: setns " << kinds[i] << ": " << strerror(errno) << std::endl; return 1; } close(fds[i]); } for (int c = 0; c < 64; ++c) if (!(bnd & (1ULL << c)) && prctl(PR_CAPBSET_DROP, c, 0, 0, 0) != 0 && errno == EINVAL) break; // the PID namespace takes effect for children signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); pid_t child = fork(); if (child < 0) { std::cerr << "containerctl: fork: " << strerror(errno) << std::endl; return 1; } if (child == 0) { signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); if (chdir("/root") != 0 && chdir("/") != 0) _exit(127); const char *term = getenv("TERM"); std::string t = std::string("TERM=") + (term && *term ? term : "xterm"); char hostname[256] = ""; gethostname(hostname, sizeof(hostname) - 1); std::string ps1 = std::string("PS1=") + hostname + ":\\w# "; const char *env[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOME=/root", "USER=root", "LOGNAME=root", t.c_str(), ps1.c_str(), nullptr}; std::vector<char *> args; if (from < argc) for (int i = from; i < argc; ++i) args.push_back(argv[i]); else args.push_back((char *)"/bin/sh"); args.push_back(nullptr); // searched in the container's PATH (execvpe() searches the // caller's: set it first) setenv("PATH", env[0] + 5, 1); execvpe(args[0], args.data(), (char **)env); if (from >= argc) { char *bash[] = {(char *)"bash", nullptr}; execvpe("bash", bash, (char **)env); } std::cerr << "containerctl: " << args[0] << ": " << strerror(errno) << std::endl; _exit(127); } int status = 0; while (waitpid(child, &status, 0) < 0 && errno == EINTR) {} return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); } int usage() { std::cerr << "usage: containerctl images | pull IMAGE | rmi IMAGE\n" " containerctl list | status NAME | log NAME [LINES] | jobs\n" " containerctl create NAME image=IMAGE [key=value ...]\n" " containerctl edit NAME key=value ...\n" " containerctl start|stop|restart|rm NAME | kill NAME [SIGNAL]\n"; " containerctl start|stop|restart|rm NAME | kill NAME [SIGNAL]\n" " containerctl shell NAME [COMMAND ...]\n"; return 2; } Loading Loading @@ -216,5 +314,6 @@ int main(int argc, char **argv) { return 0; } if (cmd == "rm") return follow(call(m::DeleteContainer, {{"name", arg}})); if (cmd == "shell") return shell(arg, argc, argv, 3); return usage(); }
src/containers.cpp +4 −1 Changes for src/containers.cpp: 4 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -291,9 +291,12 @@ Result startOne(const std::string &name) { if (!isDir(layerDir(layer))) return fail(tr("a layer of %1 is missing; pull it again", "eine Schicht von %1 fehlt; bitte neu laden", {s.image}), 500); if (s.network == "bridge" && !isDir("/sys/class/net/" + s.bridge + "/bridge")) return fail(tr("there is no bridge %1", "es gibt keine Bridge %1", {s.bridge}), 409); // a missing bind mount source is created as a directory (a file to // mount has to exist) for (auto &b : s.binds) { std::string src = b.substr(0, b.find(':')); if (!exists(src)) return fail(tr("the bind mount source %1 doesn't exist", "die Quelle %1 des Bind-Mounts gibt es nicht", {src}), 409); if (!exists(src) && !mkdirs(src, 0755)) return fail(sysErr(tr("creating the bind mount source %1", "Anlegen der Bind-Mount-Quelle %1", {src}), errno), 500); } for (auto &d : s.devices) { std::string src = d.substr(0, d.find(':')); Loading
src/devices.cpp +7 −1 Changes for src/devices.cpp: 7 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -100,10 +100,16 @@ std::string attachDeviceFilter(const std::string &cgroup, const std::vector<Devi load.insns = (uint64_t)(uintptr_t)p.data(); load.insn_cnt = (uint32_t)p.size(); load.license = (uint64_t)(uintptr_t) "GPL"; // without a verifier log first: a log longer than the buffer (a long // device list) fails the load with ENOSPC on its own int prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); if (prog < 0) { // again with the log, for the reason load.log_buf = (uint64_t)(uintptr_t)log; load.log_size = sizeof(log); load.log_level = 1; int prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); prog = (int)syscall(SYS_bpf, BPF_PROG_LOAD, &load, sizeof(load)); } if (prog < 0) { int e = errno; std::string why = sysErr("bpf(BPF_PROG_LOAD)", e); Loading
src/netlink.cpp +7 −2 Changes for src/netlink.cpp: 7 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -136,17 +136,22 @@ bool parseMac(const std::string &s, uint8_t out[6]) { } // namespace std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac) { std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac, const std::string &hostMac) { Msg m(RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL); m.fixed<ifinfomsg>()->ifi_family = AF_UNSPEC; m.str(IFLA_IFNAME, name); uint8_t mac[6]; if (!hostMac.empty()) { if (!parseMac(hostMac, mac)) return "invalid MAC address " + hostMac; m.attr(IFLA_ADDRESS, mac, 6); } size_t info = m.begin(IFLA_LINKINFO); m.str(IFLA_INFO_KIND, "veth"); size_t data = m.begin(IFLA_INFO_DATA); size_t p = m.begin(VETH_INFO_PEER); m.fixed<ifinfomsg>()->ifi_family = AF_UNSPEC; m.str(IFLA_IFNAME, peer); uint8_t mac[6]; if (!peerMac.empty()) { if (!parseMac(peerMac, mac)) return "invalid MAC address " + peerMac; m.attr(IFLA_ADDRESS, mac, 6); Loading
src/netlink.h +4 −2 Changes for src/netlink.h: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -38,8 +38,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace containerd::nl { // peerMac: "02:.." for the peer (the container's end), "" for a random one std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac = ""); // peerMac: "02:.." for the peer (the container's end), hostMac for name // (the host's end); "" for a random one std::string createVeth(const std::string &name, const std::string &peer, const std::string &peerMac = "", const std::string &hostMac = ""); std::string setMaster(const std::string &ifname, const std::string &bridge); std::string setUp(const std::string &ifname); std::string moveToNetns(const std::string &ifname, pid_t pid); Loading