Loading src/network.cpp +123 −22 Changes for src/network.cpp: 123 added lines, 22 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ #include <filesystem> #include <algorithm> #include <cstring> #include <cstdio> #include <cerrno> #include <cctype> #include <cstdlib> Loading Loading @@ -375,6 +376,13 @@ void NetworkManager::reloadConfigs() { }); } static std::string readSysfs(const std::string &ifname, const char *attr) { std::ifstream f("/sys/class/net/" + ifname + "/" + attr); std::string v; if (f.is_open()) std::getline(f, v); return trim(v); } // the bond or bridge ifname is a member of now ("" if none) static std::string currentMaster(const std::string &ifname) { std::error_code ec; Loading @@ -382,17 +390,23 @@ static std::string currentMaster(const std::string &ifname) { return ec ? std::string() : target.filename().string(); } // members first (their bond/bridge needs them before it can get an // address), then everything else // the order interfaces are configured in: members of bonds, members of // bridges (a bond in a bridge: after its own members, so it has their MAC), // the bonds/bridges themselves (right after their members: when an address // moves from a member to its bridge, the gap stays short), then the rest static std::vector<std::string> membersFirst(const std::vector<std::string> &ifnames, const std::function<const NetworkConfig *(const std::string &)> &cfgOf) { std::vector<std::string> out; for (int pass = 0; pass < 2; ++pass) for (auto &i : ifnames) { const std::function<const NetworkConfig *(const std::string &)> &cfgOf, const std::function<bool(const std::string &)> &isNetDev) { auto rank = [&](const std::string &i) { const NetworkConfig *c = cfgOf(i); bool member = c && (!c->bond.empty() || !c->bridge.empty()); if (member == (pass == 0)) out.push_back(i); } if (c && !c->bond.empty()) return 0; if (c && !c->bridge.empty()) return 1; return isNetDev(i) ? 2 : 3; }; std::vector<std::string> out; for (int pass = 0; pass < 4; ++pass) for (auto &i : ifnames) if (rank(i) == pass) out.push_back(i); return out; } Loading @@ -409,7 +423,24 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network std::cerr << "[syslite/net] " << ifname << ": " << master << " doesn't exist (yet)" << std::endl; return; } if (currentMaster(ifname) != master) { if (currentMaster(ifname) != master && cfg.bond.empty()) { // a bridge takes members as they are (up): it goes up first, so // the new port forwards at once (no STP: no listening/learning) setLinkUp(master); // a bond in a bridge: the bridge keeps the bond's MAC for good // (else it takes the lowest of its ports' and changes with them), // so switches and ARP caches see the same address as before std::error_code ec; if (std::filesystem::exists("/sys/class/net/" + ifname + "/bonding", ec) && readSysfs(master, "addr_assign_type") != "3" /* NET_ADDR_SET */) setLinkAddress(master, readSysfs(ifname, "address")); if (!setMaster(ifname, master)) std::cerr << "[syslite/net] " << ifname << ": cannot add it to " << master << std::endl; else std::cout << "[syslite/net] " << ifname << ": member of " << master << std::endl; // its addresses only now: until it is a port they still work flushIpv4Addresses(ifname); } else if (currentMaster(ifname) != master) { flushIpv4Addresses(ifname); // a bond takes only members that are down setLinkDown(ifname); Loading Loading @@ -485,7 +516,8 @@ void NetworkManager::applyAll() { applyNetDevs(); auto cfgOf = [this](const std::string &i) { return configFor(i); }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf)) { auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf, isNetDev)) { if (_stopped.count(ifname)) continue; if (const NetworkConfig *cfg = configFor(ifname)) configureInterface(ifname, *cfg); Loading @@ -496,7 +528,8 @@ void NetworkManager::applyAll() { void NetworkManager::applyNewDevices() { bool changed = false; auto cfgOf = [this](const std::string &i) { return configFor(i); }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf)) { auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf, isNetDev)) { if (_configured.count(ifname) || _stopped.count(ifname)) continue; if (const NetworkConfig *cfg = configFor(ifname)) { configureInterface(ifname, *cfg); Loading Loading @@ -530,13 +563,6 @@ bool NetworkManager::interfaceExists(const std::string &ifname) { return std::filesystem::exists("/sys/class/net/" + ifname, ec); } static std::string readSysfs(const std::string &ifname, const char *attr) { std::ifstream f("/sys/class/net/" + ifname + "/" + attr); std::string v; if (f.is_open()) std::getline(f, v); return trim(v); } InterfaceStatus NetworkManager::status(const std::string &ifname) { InterfaceStatus st; st.name = ifname; Loading Loading @@ -589,7 +615,8 @@ bool NetworkManager::reloadInterface(const std::string &ifname, std::string *err reloadConfigs(); applyNetDevs(); auto cfgOf = [this](const std::string &i) { return configFor(i); }; std::vector<std::string> targets = membersFirst(ifname.empty() ? enumInterfaces() : std::vector<std::string>{ifname}, cfgOf); auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; std::vector<std::string> targets = membersFirst(ifname.empty() ? enumInterfaces() : std::vector<std::string>{ifname}, cfgOf, isNetDev); for (auto &i : targets) { if (_stopped.count(i)) continue; const NetworkConfig *cfg = configFor(i); Loading Loading @@ -809,7 +836,11 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s for (auto &m : members) { if (!validIfname(m)) return fail("invalid interface name '" + m + "'"); if (m == nd.ifname || m == "lo") return fail(m + " can't be a member of " + nd.ifname); if (findNetDev(m)) return fail(m + " is a bond or bridge itself"); // a bond can be a port of a bridge; nothing else nests if (const NetDevConfig *inner = findNetDev(m)) if (nd.kind != "bridge" || inner->kind != "bond") return fail(m + " is a " + inner->kind + " itself" + (nd.kind == "bridge" ? " (a bridge takes bonds, not bridges)" : "")); for (auto &other : _netdevs) { if (other.ifname == nd.ifname) continue; auto mem = membersOf(other.ifname); Loading Loading @@ -852,6 +883,30 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s if (!writeNetworkFile(cfg, err)) return rollback(); } } // A bond/bridge without addresses of its own takes over those of its // first new member that has some (a bond with its address becoming a // bridge port: the address moves to the bridge, the host stays // reachable under it). Its configuration: 10-<ifname>, like webctl's. auto ownConfigOf = [&](const std::string &ifname) -> const NetworkConfig * { for (auto &c : configs) if (c.matchName == ifname && c.bond.empty() && c.bridge.empty()) return &c; return nullptr; }; if (!ownConfigOf(nd.ifname)) { const std::vector<std::string> before = membersOf(nd.ifname); for (auto &m : members) { if (std::find(before.begin(), before.end(), m) != before.end()) continue; const NetworkConfig *own = ownConfigOf(m); if (!own || own->filePath.rfind(base, 0) != 0 || (!own->dhcp && own->address.empty())) continue; NetworkConfig moved = *own; moved.name = memberConfigName(nd.ifname); moved.matchName = nd.ifname; if (!writeNetworkFile(moved, err)) return rollback(); std::cout << "[syslite/net] " << nd.ifname << " takes over the addresses of " << m << " (" << moved.name << ".network)" << std::endl; break; } } for (auto &m : members) { NetworkConfig cfg; cfg.name = memberConfigName(m); Loading @@ -876,11 +931,27 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { const std::string base = std::string(ConfigDir) + "/"; if (nd->filePath.rfind(base, 0) != 0) { if (err) *err = nd->filePath + " is outside " + ConfigDir + ", not editable"; return false; } // the reverse of saveNetDev's takeover: with a single member, that one // gets the addresses back (a bridge on a bond is removed, the bond keeps // the host's address) const std::vector<std::string> members = membersOf(ifname); const NetworkConfig *own = nullptr; for (auto &c : _configs) if (c.matchName == ifname && c.bond.empty() && c.bridge.empty() && c.filePath.rfind(base, 0) == 0) { own = &c; break; } const bool giveBack = members.size() == 1 && own && (own->dhcp || !own->address.empty()); const NetworkConfig ownCopy = own ? *own : NetworkConfig{}; std::error_code ec; for (auto cfg : _configs) { if (cfg.filePath.rfind(base, 0) != 0) continue; if (cfg.bond == ifname || cfg.bridge == ifname) { if (cfg.name == memberConfigName(cfg.matchName)) { if (giveBack && cfg.matchName == members[0]) { NetworkConfig back = ownCopy; back.name = cfg.name; back.matchName = cfg.matchName; if (!writeNetworkFile(back, err)) return false; std::cout << "[syslite/net] " << cfg.matchName << " gets the addresses of " << ifname << " back" << std::endl; } else if (cfg.name == memberConfigName(cfg.matchName)) { std::filesystem::remove(cfg.filePath, ec); } else { cfg.bond.clear(); Loading @@ -903,6 +974,13 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { } _stopped.erase(ifname); reloadConfigs(); // the member that got the addresses back has them at once, not only // with the next reload if (giveBack && interfaceExists(members[0]) && !_stopped.count(members[0])) if (const NetworkConfig *cfg = configFor(members[0])) { unconfigureInterface(members[0]); configureInterface(members[0], *cfg); } updateResolv(); return true; } Loading Loading @@ -1025,6 +1103,29 @@ bool NetworkManager::setMaster(const std::string &ifname, const std::string &mas return ok; } bool NetworkManager::setLinkAddress(const std::string &ifname, const std::string &mac) { unsigned char addr[6]; unsigned v[6]; if (std::sscanf(mac.c_str(), "%x:%x:%x:%x:%x:%x", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6) return false; for (int i = 0; i < 6; ++i) addr[i] = static_cast<unsigned char>(v[i]); int idx = getIfIndex(ifname); if (idx < 0) return false; int fd = netlinkOpen(); if (fd < 0) return false; NlMsg msg{}; msg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); msg.hdr.nlmsg_type = RTM_SETLINK; msg.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; msg.hdr.nlmsg_seq = 1; auto *ifi = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(&msg.hdr)); ifi->ifi_family = AF_UNSPEC; ifi->ifi_index = idx; nlAddAttr(&msg.hdr, sizeof(msg), IFLA_ADDRESS, addr, 6); bool ok = netlinkSend(fd, &msg.hdr); close(fd); return ok; } // ─── Netlink: addresses ───────────────────────────────────────────────────── // Calls fn for every address of interface idx (RTM_GETADDR dump). Loading src/network.h +3 −0 Changes for src/network.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -170,6 +170,9 @@ namespace syslite { bool createLink(const NetDevConfig &nd); bool deleteLink(const std::string &ifname); bool setMaster(const std::string &ifname, const std::string &master); // fixes ifname's MAC address ("aa:bb:cc:dd:ee:ff"; a bridge keeps it // instead of following its ports) bool setLinkAddress(const std::string &ifname, const std::string &mac); // does the existing device match nd (kind, bond mode, STP)? bool linkMatches(const NetDevConfig &nd); Loading Loading
src/network.cpp +123 −22 Changes for src/network.cpp: 123 added lines, 22 removed lines. Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ #include <filesystem> #include <algorithm> #include <cstring> #include <cstdio> #include <cerrno> #include <cctype> #include <cstdlib> Loading Loading @@ -375,6 +376,13 @@ void NetworkManager::reloadConfigs() { }); } static std::string readSysfs(const std::string &ifname, const char *attr) { std::ifstream f("/sys/class/net/" + ifname + "/" + attr); std::string v; if (f.is_open()) std::getline(f, v); return trim(v); } // the bond or bridge ifname is a member of now ("" if none) static std::string currentMaster(const std::string &ifname) { std::error_code ec; Loading @@ -382,17 +390,23 @@ static std::string currentMaster(const std::string &ifname) { return ec ? std::string() : target.filename().string(); } // members first (their bond/bridge needs them before it can get an // address), then everything else // the order interfaces are configured in: members of bonds, members of // bridges (a bond in a bridge: after its own members, so it has their MAC), // the bonds/bridges themselves (right after their members: when an address // moves from a member to its bridge, the gap stays short), then the rest static std::vector<std::string> membersFirst(const std::vector<std::string> &ifnames, const std::function<const NetworkConfig *(const std::string &)> &cfgOf) { std::vector<std::string> out; for (int pass = 0; pass < 2; ++pass) for (auto &i : ifnames) { const std::function<const NetworkConfig *(const std::string &)> &cfgOf, const std::function<bool(const std::string &)> &isNetDev) { auto rank = [&](const std::string &i) { const NetworkConfig *c = cfgOf(i); bool member = c && (!c->bond.empty() || !c->bridge.empty()); if (member == (pass == 0)) out.push_back(i); } if (c && !c->bond.empty()) return 0; if (c && !c->bridge.empty()) return 1; return isNetDev(i) ? 2 : 3; }; std::vector<std::string> out; for (int pass = 0; pass < 4; ++pass) for (auto &i : ifnames) if (rank(i) == pass) out.push_back(i); return out; } Loading @@ -409,7 +423,24 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network std::cerr << "[syslite/net] " << ifname << ": " << master << " doesn't exist (yet)" << std::endl; return; } if (currentMaster(ifname) != master) { if (currentMaster(ifname) != master && cfg.bond.empty()) { // a bridge takes members as they are (up): it goes up first, so // the new port forwards at once (no STP: no listening/learning) setLinkUp(master); // a bond in a bridge: the bridge keeps the bond's MAC for good // (else it takes the lowest of its ports' and changes with them), // so switches and ARP caches see the same address as before std::error_code ec; if (std::filesystem::exists("/sys/class/net/" + ifname + "/bonding", ec) && readSysfs(master, "addr_assign_type") != "3" /* NET_ADDR_SET */) setLinkAddress(master, readSysfs(ifname, "address")); if (!setMaster(ifname, master)) std::cerr << "[syslite/net] " << ifname << ": cannot add it to " << master << std::endl; else std::cout << "[syslite/net] " << ifname << ": member of " << master << std::endl; // its addresses only now: until it is a port they still work flushIpv4Addresses(ifname); } else if (currentMaster(ifname) != master) { flushIpv4Addresses(ifname); // a bond takes only members that are down setLinkDown(ifname); Loading Loading @@ -485,7 +516,8 @@ void NetworkManager::applyAll() { applyNetDevs(); auto cfgOf = [this](const std::string &i) { return configFor(i); }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf)) { auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf, isNetDev)) { if (_stopped.count(ifname)) continue; if (const NetworkConfig *cfg = configFor(ifname)) configureInterface(ifname, *cfg); Loading @@ -496,7 +528,8 @@ void NetworkManager::applyAll() { void NetworkManager::applyNewDevices() { bool changed = false; auto cfgOf = [this](const std::string &i) { return configFor(i); }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf)) { auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; for (auto &ifname : membersFirst(enumInterfaces(), cfgOf, isNetDev)) { if (_configured.count(ifname) || _stopped.count(ifname)) continue; if (const NetworkConfig *cfg = configFor(ifname)) { configureInterface(ifname, *cfg); Loading Loading @@ -530,13 +563,6 @@ bool NetworkManager::interfaceExists(const std::string &ifname) { return std::filesystem::exists("/sys/class/net/" + ifname, ec); } static std::string readSysfs(const std::string &ifname, const char *attr) { std::ifstream f("/sys/class/net/" + ifname + "/" + attr); std::string v; if (f.is_open()) std::getline(f, v); return trim(v); } InterfaceStatus NetworkManager::status(const std::string &ifname) { InterfaceStatus st; st.name = ifname; Loading Loading @@ -589,7 +615,8 @@ bool NetworkManager::reloadInterface(const std::string &ifname, std::string *err reloadConfigs(); applyNetDevs(); auto cfgOf = [this](const std::string &i) { return configFor(i); }; std::vector<std::string> targets = membersFirst(ifname.empty() ? enumInterfaces() : std::vector<std::string>{ifname}, cfgOf); auto isNetDev = [this](const std::string &i) { return findNetDev(i) != nullptr; }; std::vector<std::string> targets = membersFirst(ifname.empty() ? enumInterfaces() : std::vector<std::string>{ifname}, cfgOf, isNetDev); for (auto &i : targets) { if (_stopped.count(i)) continue; const NetworkConfig *cfg = configFor(i); Loading Loading @@ -809,7 +836,11 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s for (auto &m : members) { if (!validIfname(m)) return fail("invalid interface name '" + m + "'"); if (m == nd.ifname || m == "lo") return fail(m + " can't be a member of " + nd.ifname); if (findNetDev(m)) return fail(m + " is a bond or bridge itself"); // a bond can be a port of a bridge; nothing else nests if (const NetDevConfig *inner = findNetDev(m)) if (nd.kind != "bridge" || inner->kind != "bond") return fail(m + " is a " + inner->kind + " itself" + (nd.kind == "bridge" ? " (a bridge takes bonds, not bridges)" : "")); for (auto &other : _netdevs) { if (other.ifname == nd.ifname) continue; auto mem = membersOf(other.ifname); Loading Loading @@ -852,6 +883,30 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s if (!writeNetworkFile(cfg, err)) return rollback(); } } // A bond/bridge without addresses of its own takes over those of its // first new member that has some (a bond with its address becoming a // bridge port: the address moves to the bridge, the host stays // reachable under it). Its configuration: 10-<ifname>, like webctl's. auto ownConfigOf = [&](const std::string &ifname) -> const NetworkConfig * { for (auto &c : configs) if (c.matchName == ifname && c.bond.empty() && c.bridge.empty()) return &c; return nullptr; }; if (!ownConfigOf(nd.ifname)) { const std::vector<std::string> before = membersOf(nd.ifname); for (auto &m : members) { if (std::find(before.begin(), before.end(), m) != before.end()) continue; const NetworkConfig *own = ownConfigOf(m); if (!own || own->filePath.rfind(base, 0) != 0 || (!own->dhcp && own->address.empty())) continue; NetworkConfig moved = *own; moved.name = memberConfigName(nd.ifname); moved.matchName = nd.ifname; if (!writeNetworkFile(moved, err)) return rollback(); std::cout << "[syslite/net] " << nd.ifname << " takes over the addresses of " << m << " (" << moved.name << ".network)" << std::endl; break; } } for (auto &m : members) { NetworkConfig cfg; cfg.name = memberConfigName(m); Loading @@ -876,11 +931,27 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { const std::string base = std::string(ConfigDir) + "/"; if (nd->filePath.rfind(base, 0) != 0) { if (err) *err = nd->filePath + " is outside " + ConfigDir + ", not editable"; return false; } // the reverse of saveNetDev's takeover: with a single member, that one // gets the addresses back (a bridge on a bond is removed, the bond keeps // the host's address) const std::vector<std::string> members = membersOf(ifname); const NetworkConfig *own = nullptr; for (auto &c : _configs) if (c.matchName == ifname && c.bond.empty() && c.bridge.empty() && c.filePath.rfind(base, 0) == 0) { own = &c; break; } const bool giveBack = members.size() == 1 && own && (own->dhcp || !own->address.empty()); const NetworkConfig ownCopy = own ? *own : NetworkConfig{}; std::error_code ec; for (auto cfg : _configs) { if (cfg.filePath.rfind(base, 0) != 0) continue; if (cfg.bond == ifname || cfg.bridge == ifname) { if (cfg.name == memberConfigName(cfg.matchName)) { if (giveBack && cfg.matchName == members[0]) { NetworkConfig back = ownCopy; back.name = cfg.name; back.matchName = cfg.matchName; if (!writeNetworkFile(back, err)) return false; std::cout << "[syslite/net] " << cfg.matchName << " gets the addresses of " << ifname << " back" << std::endl; } else if (cfg.name == memberConfigName(cfg.matchName)) { std::filesystem::remove(cfg.filePath, ec); } else { cfg.bond.clear(); Loading @@ -903,6 +974,13 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { } _stopped.erase(ifname); reloadConfigs(); // the member that got the addresses back has them at once, not only // with the next reload if (giveBack && interfaceExists(members[0]) && !_stopped.count(members[0])) if (const NetworkConfig *cfg = configFor(members[0])) { unconfigureInterface(members[0]); configureInterface(members[0], *cfg); } updateResolv(); return true; } Loading Loading @@ -1025,6 +1103,29 @@ bool NetworkManager::setMaster(const std::string &ifname, const std::string &mas return ok; } bool NetworkManager::setLinkAddress(const std::string &ifname, const std::string &mac) { unsigned char addr[6]; unsigned v[6]; if (std::sscanf(mac.c_str(), "%x:%x:%x:%x:%x:%x", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6) return false; for (int i = 0; i < 6; ++i) addr[i] = static_cast<unsigned char>(v[i]); int idx = getIfIndex(ifname); if (idx < 0) return false; int fd = netlinkOpen(); if (fd < 0) return false; NlMsg msg{}; msg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); msg.hdr.nlmsg_type = RTM_SETLINK; msg.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; msg.hdr.nlmsg_seq = 1; auto *ifi = reinterpret_cast<struct ifinfomsg *>(NLMSG_DATA(&msg.hdr)); ifi->ifi_family = AF_UNSPEC; ifi->ifi_index = idx; nlAddAttr(&msg.hdr, sizeof(msg), IFLA_ADDRESS, addr, 6); bool ok = netlinkSend(fd, &msg.hdr); close(fd); return ok; } // ─── Netlink: addresses ───────────────────────────────────────────────────── // Calls fn for every address of interface idx (RTM_GETADDR dump). Loading
src/network.h +3 −0 Changes for src/network.h: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -170,6 +170,9 @@ namespace syslite { bool createLink(const NetDevConfig &nd); bool deleteLink(const std::string &ifname); bool setMaster(const std::string &ifname, const std::string &master); // fixes ifname's MAC address ("aa:bb:cc:dd:ee:ff"; a bridge keeps it // instead of following its ports) bool setLinkAddress(const std::string &ifname, const std::string &mac); // does the existing device match nd (kind, bond mode, STP)? bool linkMatches(const NetDevConfig &nd); Loading