Loading include/containerd/api.h +3 −0 Changes for include/containerd/api.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -137,6 +137,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * EditContainer {name, settings} rename: new_name (stopped only) * DeleteContainer {name} stopped only; its changes go too * StartContainer {name} -> state (running, or failed and error) * with network = host not (409) while a port its image exposes is * exposed by another running container with the host's network or * in use on the host (a listening TCP or bound UDP socket) * StopContainer {name, timeout? (s, default 10)} the image's * StopSignal (default SIGTERM) to the container, after timeout * everything in its cgroup is killed; replies at once (stopping) Loading src/containers.cpp +72 −0 Changes for src/containers.cpp: 72 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -30,10 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <algorithm> #include <cerrno> #include <csignal> #include <cstdlib> #include <cstring> #include <ctime> #include <iostream> #include <map> #include <sstream> #include <fcntl.h> #include <poll.h> Loading Loading @@ -291,6 +293,74 @@ void readStatus(const std::string &name, Runtime &r) { } } // an image's exposed port ("80/tcp", "53/udp", "8000-8010/tcp", "80"): // its protocol and range; false if it doesn't read as one bool exposedPort(const std::string &spec, std::string &proto, int &first, int &last) { size_t slash = spec.find('/'); proto = slash == std::string::npos ? "tcp" : spec.substr(slash + 1); std::string range = spec.substr(0, slash); size_t dash = range.find('-'); first = atoi(range.substr(0, dash).c_str()); last = dash == std::string::npos ? first : atoi(range.substr(dash + 1).c_str()); return (proto == "tcp" || proto == "udp") && first > 0 && first <= last && last < 65536; } // the host's ports of a protocol something listens on (tcp) or is bound // to (udp), on any address: a bind to 0.0.0.0 fails on any of them std::vector<int> hostPorts(const std::string &proto) { std::vector<int> out; for (const char *v : {"", "6"}) { std::string text; if (!readFile("/proc/net/" + proto + v, text)) continue; std::istringstream lines(text); std::string line; std::getline(lines, line); // the header while (std::getline(lines, line)) { std::istringstream f(line); std::string sl, local, remote, st; if (!(f >> sl >> local >> remote >> st)) continue; // 0A: TCP_LISTEN; 07: TCP_CLOSE, an unconnected UDP socket if (st != (proto == "tcp" ? "0A" : "07")) continue; size_t colon = local.rfind(':'); if (colon != std::string::npos) out.push_back((int)strtol(local.c_str() + colon + 1, nullptr, 16)); } } return out; } // with the host's network a container's ports are the host's: what its // image exposes mustn't be taken by another container with the host's // network or by something on the host ("" if it is free) std::string portConflict(const std::string &name, const Settings &s, const Image &image) { if (s.network != "host") return ""; for (auto &spec : image.ports) { std::string proto; int first, last; if (!exposedPort(spec, proto, first, last)) continue; std::string what = spec.substr(0, spec.find('/')) + "/" + proto; auto overlaps = [&](const std::string &other) { std::string p; int a, b; return exposedPort(other, p, a, b) && p == proto && a <= last && first <= b; }; for (auto &[other, r] : table()) { Settings os; Image oi; std::string err; if (other == name || !active(r) || !load(other, os, err) || os.network != "host" || !loadImage(os.image, oi)) continue; if (std::any_of(oi.ports.begin(), oi.ports.end(), overlaps)) return tr("port %1 is taken by the container %2 (both have the host's network; give one network=bridge)", "Port %1 ist vom Container %2 belegt (beide haben das Netz des Hosts; einem network=bridge geben)", {what, other}); } for (int port : hostPorts(proto)) if (port >= first && port <= last) return tr("port %1 is in use on the host (the container has the host's network; give it network=bridge)", "Port %1 ist auf dem Host belegt (der Container hat das Netz des Hosts; ihm network=bridge geben)", {std::to_string(port) + "/" + proto}); } return ""; } Result startOne(const std::string &name) { Settings s; std::string err; Loading @@ -310,6 +380,8 @@ 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); err = portConflict(name, s, l.image); if (!err.empty()) return fail(err, 409); // a missing bind mount source is created as a directory (a file to // mount has to exist) for (auto &b : s.binds) { Loading Loading
include/containerd/api.h +3 −0 Changes for include/containerd/api.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -137,6 +137,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * EditContainer {name, settings} rename: new_name (stopped only) * DeleteContainer {name} stopped only; its changes go too * StartContainer {name} -> state (running, or failed and error) * with network = host not (409) while a port its image exposes is * exposed by another running container with the host's network or * in use on the host (a listening TCP or bound UDP socket) * StopContainer {name, timeout? (s, default 10)} the image's * StopSignal (default SIGTERM) to the container, after timeout * everything in its cgroup is killed; replies at once (stopping) Loading
src/containers.cpp +72 −0 Changes for src/containers.cpp: 72 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -30,10 +30,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <algorithm> #include <cerrno> #include <csignal> #include <cstdlib> #include <cstring> #include <ctime> #include <iostream> #include <map> #include <sstream> #include <fcntl.h> #include <poll.h> Loading Loading @@ -291,6 +293,74 @@ void readStatus(const std::string &name, Runtime &r) { } } // an image's exposed port ("80/tcp", "53/udp", "8000-8010/tcp", "80"): // its protocol and range; false if it doesn't read as one bool exposedPort(const std::string &spec, std::string &proto, int &first, int &last) { size_t slash = spec.find('/'); proto = slash == std::string::npos ? "tcp" : spec.substr(slash + 1); std::string range = spec.substr(0, slash); size_t dash = range.find('-'); first = atoi(range.substr(0, dash).c_str()); last = dash == std::string::npos ? first : atoi(range.substr(dash + 1).c_str()); return (proto == "tcp" || proto == "udp") && first > 0 && first <= last && last < 65536; } // the host's ports of a protocol something listens on (tcp) or is bound // to (udp), on any address: a bind to 0.0.0.0 fails on any of them std::vector<int> hostPorts(const std::string &proto) { std::vector<int> out; for (const char *v : {"", "6"}) { std::string text; if (!readFile("/proc/net/" + proto + v, text)) continue; std::istringstream lines(text); std::string line; std::getline(lines, line); // the header while (std::getline(lines, line)) { std::istringstream f(line); std::string sl, local, remote, st; if (!(f >> sl >> local >> remote >> st)) continue; // 0A: TCP_LISTEN; 07: TCP_CLOSE, an unconnected UDP socket if (st != (proto == "tcp" ? "0A" : "07")) continue; size_t colon = local.rfind(':'); if (colon != std::string::npos) out.push_back((int)strtol(local.c_str() + colon + 1, nullptr, 16)); } } return out; } // with the host's network a container's ports are the host's: what its // image exposes mustn't be taken by another container with the host's // network or by something on the host ("" if it is free) std::string portConflict(const std::string &name, const Settings &s, const Image &image) { if (s.network != "host") return ""; for (auto &spec : image.ports) { std::string proto; int first, last; if (!exposedPort(spec, proto, first, last)) continue; std::string what = spec.substr(0, spec.find('/')) + "/" + proto; auto overlaps = [&](const std::string &other) { std::string p; int a, b; return exposedPort(other, p, a, b) && p == proto && a <= last && first <= b; }; for (auto &[other, r] : table()) { Settings os; Image oi; std::string err; if (other == name || !active(r) || !load(other, os, err) || os.network != "host" || !loadImage(os.image, oi)) continue; if (std::any_of(oi.ports.begin(), oi.ports.end(), overlaps)) return tr("port %1 is taken by the container %2 (both have the host's network; give one network=bridge)", "Port %1 ist vom Container %2 belegt (beide haben das Netz des Hosts; einem network=bridge geben)", {what, other}); } for (int port : hostPorts(proto)) if (port >= first && port <= last) return tr("port %1 is in use on the host (the container has the host's network; give it network=bridge)", "Port %1 ist auf dem Host belegt (der Container hat das Netz des Hosts; ihm network=bridge geben)", {std::to_string(port) + "/" + proto}); } return ""; } Result startOne(const std::string &name) { Settings s; std::string err; Loading @@ -310,6 +380,8 @@ 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); err = portConflict(name, s, l.image); if (!err.empty()) return fail(err, 409); // a missing bind mount source is created as a directory (a file to // mount has to exist) for (auto &b : s.binds) { Loading