Loading include/containerd/api.h +8 −1 Changes for include/containerd/api.h: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -91,6 +91,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * or "dhcp": containerd asks the bridge's DHCP server, keeps * the lease renewed, takes router and name servers from it * gateway its default route (not with dhcp) * vlan 1-4094 (network = bridge; 0: none): the veth is an access * port of that VLAN -- containerd turns on VLAN filtering of * the bridge (its other ports stay untagged in VLAN 1), gives * the veth the VLAN as PVID, untagged, and nothing else, and * adds it tagged to the bridge's uplinks (its ports that are * no veth and no VLAN device); the switch port must carry it * tagged * mac eth0's MAC address; default: 02:.. made from the name, * the same at every start (for DHCP reservations) * dns repeated: name servers (default: the host's) Loading @@ -109,7 +116,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ListContainers -> per container: container (name), image, state * (stopped|starting|running|stopping|exited|failed), pid (host PID of * its PID 1), started, exit_code, restarts, error?, network, * address?, veth? (the host's end of its veth pair, in the * address?, vlan?, veth? (the host's end of its veth pair, in the * bridge), lease? (the address DHCP gave it), autostart, restart, * memory (current bytes), tasks * GetContainer {name} -> container (name), state & co. as in the list, Loading src/containers.cpp +1 −0 Changes for src/containers.cpp: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -409,6 +409,7 @@ void addRecord(Result &out, const std::string &name, const Settings &s) { out.add("network", s.network); if (s.network == "bridge") { out.add("address", s.address); if (s.vlan) out.add("vlan", (long long)s.vlan); out.add("veth", hostVeth(name)); // its end on the host, while it runs } if (isDhcp(s) && active(r)) { Loading src/netlink.cpp +44 −0 Changes for src/netlink.cpp: 44 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <arpa/inet.h> #include <linux/if.h> #include <linux/if_bridge.h> #include <linux/if_link.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> Loading Loading @@ -195,6 +196,49 @@ std::string deleteLink(const std::string &ifname) { return talk(m); } std::string setVlanFiltering(const std::string &bridge) { std::string err = link(bridge, [](Msg &m, ifinfomsg *) { size_t info = m.begin(IFLA_LINKINFO); m.str(IFLA_INFO_KIND, "bridge"); size_t data = m.begin(IFLA_INFO_DATA); uint8_t on = 1; m.attr(IFLA_BR_VLAN_FILTERING, &on, 1); m.end(data); m.end(info); }); if (err.find(strerror(EOPNOTSUPP)) != std::string::npos) err += " (the kernel has no bridge VLAN filtering: CONFIG_BRIDGE_VLAN_FILTERING)"; return err.empty() ? "" : "VLAN filtering on " + err; } namespace { // RTM_SETLINK/RTM_DELLINK of family AF_BRIDGE: a VLAN of a bridge port std::string bridgeVlan(const std::string &port, int vid, bool add, uint16_t flags) { int i = index(port); if (!i) return noSuch(port); Msg m(add ? RTM_SETLINK : RTM_DELLINK, 0); auto *ifi = m.fixed<ifinfomsg>(); ifi->ifi_family = AF_BRIDGE; ifi->ifi_index = i; size_t spec = m.begin(IFLA_AF_SPEC); bridge_vlan_info info{}; info.flags = flags; info.vid = (uint16_t)vid; m.attr(IFLA_BRIDGE_VLAN_INFO, &info, sizeof(info)); m.end(spec); std::string err = talk(m); return err.empty() ? "" : port + ": VLAN " + std::to_string(vid) + ": " + err; } } // namespace std::string addBridgeVlan(const std::string &port, int vid, bool pvid) { return bridgeVlan(port, vid, true, pvid ? BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED : 0); } std::string delBridgeVlan(const std::string &port, int vid) { return bridgeVlan(port, vid, false, 0); } namespace { std::string address(const std::string &ifname, const std::string &cidr, bool add) { Loading src/netlink.h +11 −2 Changes for src/netlink.h: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* What container networking needs from rtnetlink: a veth pair, its host * end in a bridge, the other end moved into the container's network * namespace and set up there. Each function works in the network * end in a bridge (optionally as an access port of a VLAN of the bridge), * the other end moved into the container's network namespace and set up * there. Each function works in the network * namespace of the calling process and returns "" or what went wrong. */ #pragma once Loading @@ -51,4 +52,12 @@ std::string delAddress(const std::string &ifname, const std::string &cidr); std::string addDefaultRoute(const std::string &gateway); std::string deleteLink(const std::string &ifname); // Bridge VLAN filtering (802.1Q-aware bridge): turned on for bridge (all // ports stay untagged members of its default PVID, usually 1, so nothing // changes for them); vid added to port -- pvid: as its PVID, untagged on // egress (an access port), else tagged (a trunk); vid taken from port std::string setVlanFiltering(const std::string &bridge); std::string addBridgeVlan(const std::string &port, int vid, bool pvid); std::string delBridgeVlan(const std::string &port, int vid); } // namespace containerd::nl src/runtime.cpp +66 −1 Changes for src/runtime.cpp: 66 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -37,13 +37,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <map> #include <set> #include <dirent.h> #include <fcntl.h> #include <net/if.h> #include <poll.h> #include <grp.h> #include <linux/capability.h> #include <linux/ethtool.h> #include <linux/sockios.h> #include <sched.h> #include <sys/mount.h> #include <sys/ioctl.h> #include <sys/prctl.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/sysmacros.h> Loading Loading @@ -573,6 +579,62 @@ void finish(const Launch &l, int status, const std::string &line, const std::str writeAll(status, line + "\n"); } // the driver of a network device as ethtool names it ("" if unknown) std::string linkDriver(const std::string &ifname) { int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return ""; ethtool_drvinfo info{}; info.cmd = ETHTOOL_GDRVINFO; ifreq ifr{}; strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1); ifr.ifr_data = (char *)&info; int r = ioctl(fd, SIOCETHTOOL, &ifr); close(fd); return r == 0 ? std::string(info.driver) : ""; } // the bridge's uplinks: its ports that lead out -- not the veths of // containers, not the taps of VMs, not VLAN devices (their frames are // tagged by the device already) std::vector<std::string> uplinksOf(const std::string &bridge) { std::vector<std::string> out; DIR *d = opendir(("/sys/class/net/" + bridge + "/brif").c_str()); if (!d) return out; while (dirent *e = readdir(d)) { std::string port = e->d_name, uevent; if (port[0] == '.') continue; readFile("/sys/class/net/" + port + "/uevent", uevent); std::string drv = linkDriver(port); if (drv == "veth" || drv == "tun" || uevent.find("DEVTYPE=vlan") != std::string::npos) continue; out.push_back(port); } closedir(d); return out; } // VLAN=: veth, a port of the bridge, becomes an access port of vid (its // PVID, untagged, in no other VLAN); the bridge filters VLANs, its uplinks // carry vid tagged. The uplinks keep it when the container stops: another // one may use it, and a VLAN the switch doesn't send costs nothing. std::string vlanAccessPort(const std::string &bridge, const std::string &veth, int vid) { std::string on; readFile("/sys/class/net/" + bridge + "/bridge/vlan_filtering", on); if (trim(on) != "1") { std::string err = nl::setVlanFiltering(bridge); if (!err.empty()) return err; } std::string err = nl::addBridgeVlan(veth, vid, true); if (!err.empty()) return err; // the bridge's default PVID, which every new port is a member of std::string def; readFile("/sys/class/net/" + bridge + "/bridge/default_pvid", def); int defPvid = trim(def).empty() ? 1 : atoi(trim(def).c_str()); if (defPvid && defPvid != vid && !(err = nl::delBridgeVlan(veth, defPvid)).empty()) return err; for (auto &up : uplinksOf(bridge)) if (!(err = nl::addBridgeVlan(up, vid, false)).empty()) return err; return ""; } // a file the container has bind-mounted: rewritten in place (a rename // would leave the container with the old one) void rewrite(const std::string &path, const std::string &text) { Loading Loading @@ -702,7 +764,10 @@ int supervise(const Launch &l, int status) { if (s.network == "bridge") { veth = hostVeth(name); nl::deleteLink(veth); // left from a crash for (auto e : {nl::createVeth(veth, peer, macOf(s), hostMacOf(s)), nl::setMaster(veth, s.bridge), nl::setUp(veth)}) std::string e = nl::createVeth(veth, peer, macOf(s), hostMacOf(s)); if (e.empty()) e = nl::setMaster(veth, s.bridge); if (e.empty() && s.vlan) e = vlanAccessPort(s.bridge, veth, s.vlan); if (e.empty()) e = nl::setUp(veth); if (!e.empty()) { nl::deleteLink(veth); return failed(e); Loading Loading
include/containerd/api.h +8 −1 Changes for include/containerd/api.h: 8 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -91,6 +91,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * or "dhcp": containerd asks the bridge's DHCP server, keeps * the lease renewed, takes router and name servers from it * gateway its default route (not with dhcp) * vlan 1-4094 (network = bridge; 0: none): the veth is an access * port of that VLAN -- containerd turns on VLAN filtering of * the bridge (its other ports stay untagged in VLAN 1), gives * the veth the VLAN as PVID, untagged, and nothing else, and * adds it tagged to the bridge's uplinks (its ports that are * no veth and no VLAN device); the switch port must carry it * tagged * mac eth0's MAC address; default: 02:.. made from the name, * the same at every start (for DHCP reservations) * dns repeated: name servers (default: the host's) Loading @@ -109,7 +116,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ListContainers -> per container: container (name), image, state * (stopped|starting|running|stopping|exited|failed), pid (host PID of * its PID 1), started, exit_code, restarts, error?, network, * address?, veth? (the host's end of its veth pair, in the * address?, vlan?, veth? (the host's end of its veth pair, in the * bridge), lease? (the address DHCP gave it), autostart, restart, * memory (current bytes), tasks * GetContainer {name} -> container (name), state & co. as in the list, Loading
src/containers.cpp +1 −0 Changes for src/containers.cpp: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -409,6 +409,7 @@ void addRecord(Result &out, const std::string &name, const Settings &s) { out.add("network", s.network); if (s.network == "bridge") { out.add("address", s.address); if (s.vlan) out.add("vlan", (long long)s.vlan); out.add("veth", hostVeth(name)); // its end on the host, while it runs } if (isDhcp(s) && active(r)) { Loading
src/netlink.cpp +44 −0 Changes for src/netlink.cpp: 44 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <arpa/inet.h> #include <linux/if.h> #include <linux/if_bridge.h> #include <linux/if_link.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> Loading Loading @@ -195,6 +196,49 @@ std::string deleteLink(const std::string &ifname) { return talk(m); } std::string setVlanFiltering(const std::string &bridge) { std::string err = link(bridge, [](Msg &m, ifinfomsg *) { size_t info = m.begin(IFLA_LINKINFO); m.str(IFLA_INFO_KIND, "bridge"); size_t data = m.begin(IFLA_INFO_DATA); uint8_t on = 1; m.attr(IFLA_BR_VLAN_FILTERING, &on, 1); m.end(data); m.end(info); }); if (err.find(strerror(EOPNOTSUPP)) != std::string::npos) err += " (the kernel has no bridge VLAN filtering: CONFIG_BRIDGE_VLAN_FILTERING)"; return err.empty() ? "" : "VLAN filtering on " + err; } namespace { // RTM_SETLINK/RTM_DELLINK of family AF_BRIDGE: a VLAN of a bridge port std::string bridgeVlan(const std::string &port, int vid, bool add, uint16_t flags) { int i = index(port); if (!i) return noSuch(port); Msg m(add ? RTM_SETLINK : RTM_DELLINK, 0); auto *ifi = m.fixed<ifinfomsg>(); ifi->ifi_family = AF_BRIDGE; ifi->ifi_index = i; size_t spec = m.begin(IFLA_AF_SPEC); bridge_vlan_info info{}; info.flags = flags; info.vid = (uint16_t)vid; m.attr(IFLA_BRIDGE_VLAN_INFO, &info, sizeof(info)); m.end(spec); std::string err = talk(m); return err.empty() ? "" : port + ": VLAN " + std::to_string(vid) + ": " + err; } } // namespace std::string addBridgeVlan(const std::string &port, int vid, bool pvid) { return bridgeVlan(port, vid, true, pvid ? BRIDGE_VLAN_INFO_PVID | BRIDGE_VLAN_INFO_UNTAGGED : 0); } std::string delBridgeVlan(const std::string &port, int vid) { return bridgeVlan(port, vid, false, 0); } namespace { std::string address(const std::string &ifname, const std::string &cidr, bool add) { Loading
src/netlink.h +11 −2 Changes for src/netlink.h: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ /* What container networking needs from rtnetlink: a veth pair, its host * end in a bridge, the other end moved into the container's network * namespace and set up there. Each function works in the network * end in a bridge (optionally as an access port of a VLAN of the bridge), * the other end moved into the container's network namespace and set up * there. Each function works in the network * namespace of the calling process and returns "" or what went wrong. */ #pragma once Loading @@ -51,4 +52,12 @@ std::string delAddress(const std::string &ifname, const std::string &cidr); std::string addDefaultRoute(const std::string &gateway); std::string deleteLink(const std::string &ifname); // Bridge VLAN filtering (802.1Q-aware bridge): turned on for bridge (all // ports stay untagged members of its default PVID, usually 1, so nothing // changes for them); vid added to port -- pvid: as its PVID, untagged on // egress (an access port), else tagged (a trunk); vid taken from port std::string setVlanFiltering(const std::string &bridge); std::string addBridgeVlan(const std::string &port, int vid, bool pvid); std::string delBridgeVlan(const std::string &port, int vid); } // namespace containerd::nl
src/runtime.cpp +66 −1 Changes for src/runtime.cpp: 66 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -37,13 +37,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <map> #include <set> #include <dirent.h> #include <fcntl.h> #include <net/if.h> #include <poll.h> #include <grp.h> #include <linux/capability.h> #include <linux/ethtool.h> #include <linux/sockios.h> #include <sched.h> #include <sys/mount.h> #include <sys/ioctl.h> #include <sys/prctl.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/syscall.h> #include <sys/sysmacros.h> Loading Loading @@ -573,6 +579,62 @@ void finish(const Launch &l, int status, const std::string &line, const std::str writeAll(status, line + "\n"); } // the driver of a network device as ethtool names it ("" if unknown) std::string linkDriver(const std::string &ifname) { int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) return ""; ethtool_drvinfo info{}; info.cmd = ETHTOOL_GDRVINFO; ifreq ifr{}; strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1); ifr.ifr_data = (char *)&info; int r = ioctl(fd, SIOCETHTOOL, &ifr); close(fd); return r == 0 ? std::string(info.driver) : ""; } // the bridge's uplinks: its ports that lead out -- not the veths of // containers, not the taps of VMs, not VLAN devices (their frames are // tagged by the device already) std::vector<std::string> uplinksOf(const std::string &bridge) { std::vector<std::string> out; DIR *d = opendir(("/sys/class/net/" + bridge + "/brif").c_str()); if (!d) return out; while (dirent *e = readdir(d)) { std::string port = e->d_name, uevent; if (port[0] == '.') continue; readFile("/sys/class/net/" + port + "/uevent", uevent); std::string drv = linkDriver(port); if (drv == "veth" || drv == "tun" || uevent.find("DEVTYPE=vlan") != std::string::npos) continue; out.push_back(port); } closedir(d); return out; } // VLAN=: veth, a port of the bridge, becomes an access port of vid (its // PVID, untagged, in no other VLAN); the bridge filters VLANs, its uplinks // carry vid tagged. The uplinks keep it when the container stops: another // one may use it, and a VLAN the switch doesn't send costs nothing. std::string vlanAccessPort(const std::string &bridge, const std::string &veth, int vid) { std::string on; readFile("/sys/class/net/" + bridge + "/bridge/vlan_filtering", on); if (trim(on) != "1") { std::string err = nl::setVlanFiltering(bridge); if (!err.empty()) return err; } std::string err = nl::addBridgeVlan(veth, vid, true); if (!err.empty()) return err; // the bridge's default PVID, which every new port is a member of std::string def; readFile("/sys/class/net/" + bridge + "/bridge/default_pvid", def); int defPvid = trim(def).empty() ? 1 : atoi(trim(def).c_str()); if (defPvid && defPvid != vid && !(err = nl::delBridgeVlan(veth, defPvid)).empty()) return err; for (auto &up : uplinksOf(bridge)) if (!(err = nl::addBridgeVlan(up, vid, false)).empty()) return err; return ""; } // a file the container has bind-mounted: rewritten in place (a rename // would leave the container with the old one) void rewrite(const std::string &path, const std::string &text) { Loading Loading @@ -702,7 +764,10 @@ int supervise(const Launch &l, int status) { if (s.network == "bridge") { veth = hostVeth(name); nl::deleteLink(veth); // left from a crash for (auto e : {nl::createVeth(veth, peer, macOf(s), hostMacOf(s)), nl::setMaster(veth, s.bridge), nl::setUp(veth)}) std::string e = nl::createVeth(veth, peer, macOf(s), hostMacOf(s)); if (e.empty()) e = nl::setMaster(veth, s.bridge); if (e.empty() && s.vlan) e = vlanAccessPort(s.bridge, veth, s.vlan); if (e.empty()) e = nl::setUp(veth); if (!e.empty()) { nl::deleteLink(veth); return failed(e); Loading