Loading include/sysbus/client.h +11 −2 Changes for include/sysbus/client.h: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -140,7 +140,7 @@ struct InterfaceInfo { bool adminUp = false; bool carrier = false; bool stopped = false; // stopped on request: stays down std::vector<std::string> addresses; // current, "a.b.c.d/len" std::vector<std::string> addresses; // current, "a.b.c.d/len", IPv6 too // the configuration that applies (first matching .network file), if any std::string config; // file name without ".network" std::string configPath; Loading @@ -148,6 +148,9 @@ struct InterfaceInfo { bool dhcp = false; std::string configAddress; // [Network] Address= std::string gateway; std::string configAddress6; // [Network] Address= (IPv6) std::string gateway6; std::string ipv6AcceptRA; // [Network] IPv6AcceptRA=: yes, no, "" (the kernel's default) std::vector<std::string> dns; std::string kind; // "bond", "bridge", "vlan", "wireguard", "veth" or "" (what the device is) std::string master; // the bond/bridge it is a member of now Loading Loading @@ -240,7 +243,10 @@ struct WireGuardKeys { // that interface unless config/match say otherwise. // Edit: changes only the fields that are set; the target is config if // given, else the configuration that applies to interface. An empty // address/gateway, or an empty dns list, clears it. // address/gateway, or an empty dns list, clears it. address6/gateway6: // IPv6, next to DHCP or an IPv4 address; ipv6AcceptRA: yes (router // advertisements: default route, SLAAC address), no, "" (the kernel's // default). struct InterfaceSettings { std::string interface; std::string config; Loading @@ -248,6 +254,9 @@ struct InterfaceSettings { std::optional<bool> dhcp; std::optional<std::string> address; std::optional<std::string> gateway; std::optional<std::string> address6; std::optional<std::string> gateway6; std::optional<std::string> ipv6AcceptRA; std::optional<std::vector<std::string>> dns; }; Loading src/network.cpp +85 −28 Changes for src/network.cpp: 85 added lines, 28 removed lines. Original line number Diff line number Diff line Loading @@ -230,10 +230,14 @@ bool NetworkManager::parseNetworkFile(const std::string &path, NetworkConfig &cf cfg.dhcp = (val == "yes" || val == "ipv4" || val == "true"); } else if (key == "Address") { cfg.address = val; (val.find(':') != std::string::npos ? cfg.address6 : cfg.address) = val; } else if (key == "Gateway") { cfg.gateway = val; (val.find(':') != std::string::npos ? cfg.gateway6 : cfg.gateway) = val; } else if (key == "IPv6AcceptRA") { cfg.ipv6AcceptRA = val == "yes" || val == "true" || val == "1" ? "yes" : val == "no" || val == "false" || val == "0" ? "no" : ""; } else if (key == "DNS") { cfg.dns.push_back(val); Loading Loading @@ -585,9 +589,9 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network 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); flushAddresses(ifname); } else if (currentMaster(ifname) != master) { flushIpv4Addresses(ifname); flushAddresses(ifname); // a bond takes only members that are down setLinkDown(ifname); if (!setMaster(ifname, master)) Loading @@ -600,7 +604,8 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network return; } // Bring interface up // Bring interface up (IPv6AcceptRA= first: SLAAC starts with the link) setIpv6Sysctls(ifname, cfg); if (cfg.linkUp) setLinkUp(ifname); Loading Loading @@ -633,6 +638,19 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network for (auto &d : cfg.dns) _dnsByIf[ifname].push_back(d); } // IPv6: static, next to DHCPv4 or an IPv4 address if (!cfg.address6.empty()) { if (addAddress(ifname, cfg.address6)) std::cout << "[syslite/net] " << ifname << ": " << cfg.address6 << std::endl; else std::cerr << "[syslite/net] " << ifname << ": cannot add " << cfg.address6 << std::endl; } if (!cfg.gateway6.empty()) { if (addRoute(ifname, cfg.gateway6)) std::cout << "[syslite/net] " << ifname << ": gw " << cfg.gateway6 << std::endl; else std::cerr << "[syslite/net] " << ifname << ": cannot add the route via " << cfg.gateway6 << std::endl; } // a WireGuard interface: the routes to its peers (after the addresses: // the last IPv4 address going takes the device's routes with it) if (const NetDevConfig *nd = findNetDev(ifname)) Loading @@ -641,7 +659,7 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network } void NetworkManager::unconfigureInterface(const std::string &ifname) { flushIpv4Addresses(ifname); flushAddresses(ifname); _dnsByIf.erase(ifname); _configured.erase(ifname); } Loading Loading @@ -844,6 +862,11 @@ static bool validConfigName(const std::string &name) { return true; } // addresses of its own: DHCP, static IPv4 or IPv6, or SLAAC static bool hasAddresses(const NetworkConfig &cfg) { return cfg.dhcp || !cfg.address.empty() || !cfg.address6.empty() || cfg.ipv6AcceptRA == "yes"; } // one value per line: a value may not smuggle in further keys or sections static bool validValue(const std::string &v) { return v.find_first_of("\r\n") == std::string::npos; Loading Loading @@ -888,7 +911,7 @@ static bool writeAtomically(const std::string &path, const std::string &text, st bool NetworkManager::writeNetworkFile(const NetworkConfig &cfg, std::string *err) { if (!validConfigName(cfg.name)) { if (err) *err = "invalid configuration name '" + cfg.name + "'"; return false; } if (!validValue(cfg.matchName) || !validValue(cfg.address) || !validValue(cfg.gateway) || !validValue(cfg.bond) || !validValue(cfg.bridge)) { !validValue(cfg.address6) || !validValue(cfg.gateway6) || !validValue(cfg.bond) || !validValue(cfg.bridge)) { if (err) *err = "values must be single lines"; return false; } Loading @@ -911,6 +934,9 @@ bool NetworkManager::writeNetworkFile(const NetworkConfig &cfg, std::string *err << "DHCP=" << (cfg.dhcp ? "yes" : "no") << "\n"; if (!cfg.address.empty()) f << "Address=" << cfg.address << "\n"; if (!cfg.gateway.empty()) f << "Gateway=" << cfg.gateway << "\n"; if (!cfg.address6.empty()) f << "Address=" << cfg.address6 << "\n"; if (!cfg.gateway6.empty()) f << "Gateway=" << cfg.gateway6 << "\n"; if (!cfg.ipv6AcceptRA.empty()) f << "IPv6AcceptRA=" << cfg.ipv6AcceptRA << "\n"; for (auto &d : cfg.dns) f << "DNS=" << d << "\n"; if (!cfg.bond.empty()) f << "Bond=" << cfg.bond << "\n"; if (!cfg.bridge.empty()) f << "Bridge=" << cfg.bridge << "\n"; Loading Loading @@ -1171,7 +1197,7 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s 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; if (!own || own->filePath.rfind(base, 0) != 0 || !hasAddresses(*own)) continue; NetworkConfig moved = *own; moved.name = memberConfigName(nd.ifname); moved.matchName = nd.ifname; Loading @@ -1190,6 +1216,9 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s cfg.dhcp = false; cfg.address.clear(); cfg.gateway.clear(); cfg.address6.clear(); cfg.gateway6.clear(); cfg.ipv6AcceptRA.clear(); cfg.dns.clear(); cfg.bond = nd.kind == "bond" ? nd.ifname : ""; cfg.bridge = nd.kind == "bridge" ? nd.ifname : ""; Loading Loading @@ -1246,7 +1275,8 @@ bool NetworkManager::writeNetDevFile(const NetDevConfig &nd, std::string *err) { // else in it): once its last VLAN is gone, it goes too static bool onlyCarriedVlans(const NetworkConfig &cfg) { return cfg.vlans.empty() && cfg.name == "10-" + cfg.matchName && !cfg.dhcp && cfg.address.empty() && cfg.gateway.empty() && cfg.dns.empty() && cfg.bond.empty() && cfg.bridge.empty(); cfg.gateway.empty() && cfg.address6.empty() && cfg.gateway6.empty() && cfg.ipv6AcceptRA.empty() && cfg.dns.empty() && cfg.bond.empty() && cfg.bridge.empty(); } // VLAN=<nd.ifname> in the own configuration of nd.parent (10-<parent>: a Loading Loading @@ -1310,7 +1340,7 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { 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 bool giveBack = members.size() == 1 && own && hasAddresses(*own); const NetworkConfig ownCopy = own ? *own : NetworkConfig{}; std::error_code ec; Loading Loading @@ -1697,16 +1727,20 @@ std::vector<std::string> NetworkManager::listAddresses(const std::string &ifname return out; } void NetworkManager::flushIpv4Addresses(const std::string &ifname) { void NetworkManager::flushAddresses(const std::string &ifname) { int idx = getIfIndex(ifname); if (idx < 0) return; struct Addr { struct ifaddrmsg ifa; uint32_t local, address; }; struct Addr { struct ifaddrmsg ifa; uint8_t local[16], address[16]; }; std::vector<Addr> addrs; forEachAddress(idx, [&](const struct ifaddrmsg &ifa, const void *local, const void *address) { if (ifa.ifa_family != AF_INET || !local) return; Addr a{ifa, 0, 0}; std::memcpy(&a.local, local, 4); std::memcpy(&a.address, address ? address : local, 4); // IPv6: the global ones (static, SLAAC); the link-local one belongs // to the link if (!local || (ifa.ifa_family != AF_INET && (ifa.ifa_family != AF_INET6 || ifa.ifa_scope != RT_SCOPE_UNIVERSE))) return; Addr a{ifa, {0}, {0}}; const size_t len = ifa.ifa_family == AF_INET ? 4 : 16; std::memcpy(a.local, local, len); std::memcpy(a.address, address ? address : local, len); addrs.push_back(a); }); for (auto &a : addrs) { Loading @@ -1719,13 +1753,31 @@ void NetworkManager::flushIpv4Addresses(const std::string &ifname) { msg.hdr.nlmsg_seq = 1; auto *ifa = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(&msg.hdr)); *ifa = a.ifa; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, &a.local, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, &a.address, 4); const int len = a.ifa.ifa_family == AF_INET ? 4 : 16; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, a.local, len); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, a.address, len); netlinkSend(fd, &msg.hdr); close(fd); } } // ─── IPv6 sysctls ─────────────────────────────────────────────────────────── void NetworkManager::setIpv6Sysctls(const std::string &ifname, const NetworkConfig &cfg) { if (cfg.ipv6AcceptRA.empty()) return; const std::string conf = "/proc/sys/net/ipv6/conf/" + ifname + "/"; auto set = [&](const char *key, const char *value) { int fd = ::open((conf + key).c_str(), O_WRONLY | O_CLOEXEC); bool ok = fd >= 0 && ::write(fd, value, std::strlen(value)) == static_cast<ssize_t>(std::strlen(value)); if (fd >= 0) ::close(fd); if (!ok) std::cerr << "[syslite/net] " << ifname << ": cannot set " << conf << key << std::endl; }; // 2: also with forwarding on (a host with a bridge for containers) const bool on = cfg.ipv6AcceptRA == "yes"; set("accept_ra", on ? "2" : "0"); set("autoconf", on ? "1" : "0"); } // ─── Netlink: set link up ─────────────────────────────────────────────────── bool NetworkManager::setLinkUp(const std::string &ifname) { Loading Loading @@ -1782,15 +1834,17 @@ bool NetworkManager::addAddress(const std::string &ifname, const std::string &ad int idx = getIfIndex(ifname); if (idx < 0) return false; // Parse "192.168.1.10/24" // Parse "192.168.1.10/24" or "2001:db8::10/64" auto slash = addrCidr.find('/'); if (slash == std::string::npos) return false; std::string ipStr = addrCidr.substr(0, slash); int prefixLen = std::atoi(addrCidr.substr(slash + 1).c_str()); uint32_t ip = parseIpv4(ipStr); if (ip == 0 && ipStr != "0.0.0.0") return false; const bool v6 = ipStr.find(':') != std::string::npos; uint8_t ip[16] = {0}; if (inet_pton(v6 ? AF_INET6 : AF_INET, ipStr.c_str(), ip) != 1) return false; if (prefixLen < 0 || prefixLen > (v6 ? 128 : 32)) return false; int fd = netlinkOpen(); if (fd < 0) return false; Loading @@ -1802,13 +1856,13 @@ bool NetworkManager::addAddress(const std::string &ifname, const std::string &ad msg.hdr.nlmsg_seq = 1; auto *ifa = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(&msg.hdr)); ifa->ifa_family = AF_INET; ifa->ifa_family = v6 ? AF_INET6 : AF_INET; ifa->ifa_prefixlen = prefixLen; ifa->ifa_index = idx; ifa->ifa_scope = RT_SCOPE_UNIVERSE; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, &ip, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, &ip, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, ip, v6 ? 16 : 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, ip, v6 ? 16 : 4); bool ok = netlinkSend(fd, &msg.hdr); close(fd); Loading Loading @@ -1857,8 +1911,11 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate int idx = getIfIndex(ifname); if (idx < 0) return false; uint32_t gw = parseIpv4(gateway); if (gw == 0) return false; // IPv4 or IPv6 (a link-local one too: the route names the interface) const bool v6 = gateway.find(':') != std::string::npos; uint8_t gw[16] = {0}; if (inet_pton(v6 ? AF_INET6 : AF_INET, gateway.c_str(), gw) != 1) return false; if (!v6 && parseIpv4(gateway) == 0) return false; int fd = netlinkOpen(); if (fd < 0) return false; Loading @@ -1870,7 +1927,7 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate msg.hdr.nlmsg_seq = 1; auto *rtm = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(&msg.hdr)); rtm->rtm_family = AF_INET; rtm->rtm_family = v6 ? AF_INET6 : AF_INET; rtm->rtm_dst_len = 0; // default route = /0 rtm->rtm_src_len = 0; rtm->rtm_table = RT_TABLE_MAIN; Loading @@ -1878,7 +1935,7 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate rtm->rtm_scope = RT_SCOPE_UNIVERSE; rtm->rtm_type = RTN_UNICAST; nlAddAttr(&msg.hdr, sizeof(msg), RTA_GATEWAY, &gw, 4); nlAddAttr(&msg.hdr, sizeof(msg), RTA_GATEWAY, gw, v6 ? 16 : 4); int oifIdx = idx; nlAddAttr(&msg.hdr, sizeof(msg), RTA_OIF, &oifIdx, 4); Loading src/network.h +13 −3 Changes for src/network.h: 13 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -20,6 +20,13 @@ namespace syslite { bool dhcp = false; // DHCP=yes/ipv4 std::string address; // Address=192.168.1.10/24 std::string gateway; // Gateway=192.168.1.1 // Address=/Gateway= once more for IPv6 (told apart by the ':') std::string address6; // Address=2001:db8::10/64 std::string gateway6; // Gateway=fe80::1 (link-local or global) // IPv6AcceptRA=yes|no: router advertisements (default routes and // SLAAC addresses), also with forwarding on; "": the kernel's // default (accept_ra and autoconf as they are) std::string ipv6AcceptRA; std::vector<std::string> dns; // DNS=8.8.8.8 // [Network] Bond=/Bridge=: the interface is a member of that bond or Loading Loading @@ -258,12 +265,15 @@ namespace syslite { // Apply cfg to one interface (link up, DHCP or static address/route, // DNS servers); records it as configured void configureInterface(const std::string &ifname, const NetworkConfig &cfg); // Undo it: delete its IPv4 addresses (and with them its routes), drop // Undo it: delete its addresses (and with them its routes), drop // its DNS servers void unconfigureInterface(const std::string &ifname); // Netlink: the interface's addresses; delete its IPv4 addresses // Netlink: the interface's addresses; delete its IPv4 and global // IPv6 addresses (the IPv6 link-local one stays) std::vector<std::string> listAddresses(const std::string &ifname); void flushIpv4Addresses(const std::string &ifname); void flushAddresses(const std::string &ifname); // the IPv6 sysctls of cfg (IPv6AcceptRA=): before the link goes up void setIpv6Sysctls(const std::string &ifname, const NetworkConfig &cfg); // Helper: get interface index int getIfIndex(const std::string &ifname); Loading src/sysbus/client.cpp +6 −0 Changes for src/sysbus/client.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -71,6 +71,9 @@ InterfaceInfo interfaceFromFields(const std::vector<std::pair<std::string, std:: else if (k == "dhcp") i.dhcp = v == "yes"; else if (k == "config_address") i.configAddress = v; else if (k == "gateway") i.gateway = v; else if (k == "config_address6") i.configAddress6 = v; else if (k == "gateway6") i.gateway6 = v; else if (k == "ipv6_accept_ra") i.ipv6AcceptRA = v; else if (k == "dns") i.dns.push_back(v); else if (k == "kind") i.kind = v; else if (k == "master") i.master = v; Loading Loading @@ -318,6 +321,9 @@ static std::vector<std::pair<std::string, std::string>> settingsArgs(const Inter if (s.dhcp) a.emplace_back("dhcp", *s.dhcp ? "yes" : "no"); if (s.address) a.emplace_back("address", *s.address); if (s.gateway) a.emplace_back("gateway", *s.gateway); if (s.address6) a.emplace_back("address6", *s.address6); if (s.gateway6) a.emplace_back("gateway6", *s.gateway6); if (s.ipv6AcceptRA) a.emplace_back("ipv6_accept_ra", *s.ipv6AcceptRA); if (s.dns) { a.emplace_back("set_dns", "yes"); // also when the list is empty: clears it for (auto &d : *s.dns) a.emplace_back("dns", d); Loading src/syslite.cpp +36 −1 Changes for src/syslite.cpp: 36 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -10,8 +10,10 @@ #include <filesystem> #include <cstring> #include <csignal> #include <cstdlib> #include <unistd.h> #include <arpa/inet.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/mount.h> Loading Loading @@ -379,6 +381,31 @@ namespace syslite { return r; } // what is wrong with cfg's addresses ("" if nothing): Address= and // Gateway= per address family, IPv6AcceptRA= yes, no or unset static std::string checkAddresses(const NetworkConfig &cfg) { auto ip = [](int fam, const std::string &s) { unsigned char buf[16]; return inet_pton(fam, s.c_str(), buf) == 1; }; auto cidr = [&](int fam, const std::string &s) { size_t slash = s.find('/'); if (slash == std::string::npos || !ip(fam, s.substr(0, slash))) return false; std::string len = s.substr(slash + 1); if (len.empty() || len.size() > 3 || len.find_first_not_of("0123456789") != std::string::npos) return false; return std::atoi(len.c_str()) <= (fam == AF_INET ? 32 : 128); }; if (!cfg.address.empty() && !cidr(AF_INET, cfg.address)) return "invalid address '" + cfg.address + "' (a.b.c.d/len)"; if (!cfg.gateway.empty() && !ip(AF_INET, cfg.gateway)) return "invalid gateway '" + cfg.gateway + "'"; if (!cfg.address6.empty() && !cidr(AF_INET6, cfg.address6)) return "invalid IPv6 address '" + cfg.address6 + "' (2001:db8::10/64)"; if (!cfg.gateway6.empty() && !ip(AF_INET6, cfg.gateway6)) return "invalid IPv6 gateway '" + cfg.gateway6 + "'"; if (!cfg.ipv6AcceptRA.empty() && cfg.ipv6AcceptRA != "yes" && cfg.ipv6AcceptRA != "no") return "ipv6_accept_ra takes yes, no or nothing (the kernel's default)"; return ""; } static void addInterfaceFields(sysbus::Message &m, const InterfaceStatus &st) { m.add("interface", st.name); m.add("mac", st.mac); Loading @@ -397,6 +424,9 @@ namespace syslite { m.add("dhcp", cfg->dhcp ? "yes" : "no"); m.add("config_address", cfg->address); m.add("gateway", cfg->gateway); m.add("config_address6", cfg->address6); m.add("gateway6", cfg->gateway6); m.add("ipv6_accept_ra", cfg->ipv6AcceptRA); for (auto &d : cfg->dns) m.add("dns", d); if (!cfg->bond.empty()) m.add("member_of", cfg->bond); else if (!cfg->bridge.empty()) m.add("member_of", cfg->bridge); Loading Loading @@ -478,12 +508,17 @@ namespace syslite { if (call.has("dhcp")) cfg.dhcp = call.get("dhcp") == "yes"; if (call.has("address")) cfg.address = call.get("address"); if (call.has("gateway")) cfg.gateway = call.get("gateway"); if (call.has("address6")) cfg.address6 = call.get("address6"); if (call.has("gateway6")) cfg.gateway6 = call.get("gateway6"); if (call.has("ipv6_accept_ra")) cfg.ipv6AcceptRA = call.get("ipv6_accept_ra"); if (call.has("set_dns")) cfg.dns = call.getAll("dns"); if (cfg.matchName.empty()) return Message::error(call, err::InvalidArgs, "a configuration needs a match (interface name or pattern)"); if (std::string bad = checkAddresses(cfg); !bad.empty()) return Message::error(call, err::InvalidArgs, bad); // without addresses only what carries others: a VLAN parent, a // bond/bridge/VLAN (layer 2 only, e.g. for containers) if (!cfg.dhcp && cfg.address.empty() && cfg.bond.empty() && cfg.bridge.empty() && cfg.vlans.empty() && if (!cfg.dhcp && cfg.address.empty() && cfg.address6.empty() && cfg.ipv6AcceptRA != "yes" && cfg.bond.empty() && cfg.bridge.empty() && cfg.vlans.empty() && !_network.findNetDev(cfg.matchName)) return Message::error(call, err::InvalidArgs, "a configuration without DHCP needs an address"); return done(_network.saveConfig(cfg, &why), {}); Loading Loading
include/sysbus/client.h +11 −2 Changes for include/sysbus/client.h: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -140,7 +140,7 @@ struct InterfaceInfo { bool adminUp = false; bool carrier = false; bool stopped = false; // stopped on request: stays down std::vector<std::string> addresses; // current, "a.b.c.d/len" std::vector<std::string> addresses; // current, "a.b.c.d/len", IPv6 too // the configuration that applies (first matching .network file), if any std::string config; // file name without ".network" std::string configPath; Loading @@ -148,6 +148,9 @@ struct InterfaceInfo { bool dhcp = false; std::string configAddress; // [Network] Address= std::string gateway; std::string configAddress6; // [Network] Address= (IPv6) std::string gateway6; std::string ipv6AcceptRA; // [Network] IPv6AcceptRA=: yes, no, "" (the kernel's default) std::vector<std::string> dns; std::string kind; // "bond", "bridge", "vlan", "wireguard", "veth" or "" (what the device is) std::string master; // the bond/bridge it is a member of now Loading Loading @@ -240,7 +243,10 @@ struct WireGuardKeys { // that interface unless config/match say otherwise. // Edit: changes only the fields that are set; the target is config if // given, else the configuration that applies to interface. An empty // address/gateway, or an empty dns list, clears it. // address/gateway, or an empty dns list, clears it. address6/gateway6: // IPv6, next to DHCP or an IPv4 address; ipv6AcceptRA: yes (router // advertisements: default route, SLAAC address), no, "" (the kernel's // default). struct InterfaceSettings { std::string interface; std::string config; Loading @@ -248,6 +254,9 @@ struct InterfaceSettings { std::optional<bool> dhcp; std::optional<std::string> address; std::optional<std::string> gateway; std::optional<std::string> address6; std::optional<std::string> gateway6; std::optional<std::string> ipv6AcceptRA; std::optional<std::vector<std::string>> dns; }; Loading
src/network.cpp +85 −28 Changes for src/network.cpp: 85 added lines, 28 removed lines. Original line number Diff line number Diff line Loading @@ -230,10 +230,14 @@ bool NetworkManager::parseNetworkFile(const std::string &path, NetworkConfig &cf cfg.dhcp = (val == "yes" || val == "ipv4" || val == "true"); } else if (key == "Address") { cfg.address = val; (val.find(':') != std::string::npos ? cfg.address6 : cfg.address) = val; } else if (key == "Gateway") { cfg.gateway = val; (val.find(':') != std::string::npos ? cfg.gateway6 : cfg.gateway) = val; } else if (key == "IPv6AcceptRA") { cfg.ipv6AcceptRA = val == "yes" || val == "true" || val == "1" ? "yes" : val == "no" || val == "false" || val == "0" ? "no" : ""; } else if (key == "DNS") { cfg.dns.push_back(val); Loading Loading @@ -585,9 +589,9 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network 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); flushAddresses(ifname); } else if (currentMaster(ifname) != master) { flushIpv4Addresses(ifname); flushAddresses(ifname); // a bond takes only members that are down setLinkDown(ifname); if (!setMaster(ifname, master)) Loading @@ -600,7 +604,8 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network return; } // Bring interface up // Bring interface up (IPv6AcceptRA= first: SLAAC starts with the link) setIpv6Sysctls(ifname, cfg); if (cfg.linkUp) setLinkUp(ifname); Loading Loading @@ -633,6 +638,19 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network for (auto &d : cfg.dns) _dnsByIf[ifname].push_back(d); } // IPv6: static, next to DHCPv4 or an IPv4 address if (!cfg.address6.empty()) { if (addAddress(ifname, cfg.address6)) std::cout << "[syslite/net] " << ifname << ": " << cfg.address6 << std::endl; else std::cerr << "[syslite/net] " << ifname << ": cannot add " << cfg.address6 << std::endl; } if (!cfg.gateway6.empty()) { if (addRoute(ifname, cfg.gateway6)) std::cout << "[syslite/net] " << ifname << ": gw " << cfg.gateway6 << std::endl; else std::cerr << "[syslite/net] " << ifname << ": cannot add the route via " << cfg.gateway6 << std::endl; } // a WireGuard interface: the routes to its peers (after the addresses: // the last IPv4 address going takes the device's routes with it) if (const NetDevConfig *nd = findNetDev(ifname)) Loading @@ -641,7 +659,7 @@ void NetworkManager::configureInterface(const std::string &ifname, const Network } void NetworkManager::unconfigureInterface(const std::string &ifname) { flushIpv4Addresses(ifname); flushAddresses(ifname); _dnsByIf.erase(ifname); _configured.erase(ifname); } Loading Loading @@ -844,6 +862,11 @@ static bool validConfigName(const std::string &name) { return true; } // addresses of its own: DHCP, static IPv4 or IPv6, or SLAAC static bool hasAddresses(const NetworkConfig &cfg) { return cfg.dhcp || !cfg.address.empty() || !cfg.address6.empty() || cfg.ipv6AcceptRA == "yes"; } // one value per line: a value may not smuggle in further keys or sections static bool validValue(const std::string &v) { return v.find_first_of("\r\n") == std::string::npos; Loading Loading @@ -888,7 +911,7 @@ static bool writeAtomically(const std::string &path, const std::string &text, st bool NetworkManager::writeNetworkFile(const NetworkConfig &cfg, std::string *err) { if (!validConfigName(cfg.name)) { if (err) *err = "invalid configuration name '" + cfg.name + "'"; return false; } if (!validValue(cfg.matchName) || !validValue(cfg.address) || !validValue(cfg.gateway) || !validValue(cfg.bond) || !validValue(cfg.bridge)) { !validValue(cfg.address6) || !validValue(cfg.gateway6) || !validValue(cfg.bond) || !validValue(cfg.bridge)) { if (err) *err = "values must be single lines"; return false; } Loading @@ -911,6 +934,9 @@ bool NetworkManager::writeNetworkFile(const NetworkConfig &cfg, std::string *err << "DHCP=" << (cfg.dhcp ? "yes" : "no") << "\n"; if (!cfg.address.empty()) f << "Address=" << cfg.address << "\n"; if (!cfg.gateway.empty()) f << "Gateway=" << cfg.gateway << "\n"; if (!cfg.address6.empty()) f << "Address=" << cfg.address6 << "\n"; if (!cfg.gateway6.empty()) f << "Gateway=" << cfg.gateway6 << "\n"; if (!cfg.ipv6AcceptRA.empty()) f << "IPv6AcceptRA=" << cfg.ipv6AcceptRA << "\n"; for (auto &d : cfg.dns) f << "DNS=" << d << "\n"; if (!cfg.bond.empty()) f << "Bond=" << cfg.bond << "\n"; if (!cfg.bridge.empty()) f << "Bridge=" << cfg.bridge << "\n"; Loading Loading @@ -1171,7 +1197,7 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s 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; if (!own || own->filePath.rfind(base, 0) != 0 || !hasAddresses(*own)) continue; NetworkConfig moved = *own; moved.name = memberConfigName(nd.ifname); moved.matchName = nd.ifname; Loading @@ -1190,6 +1216,9 @@ bool NetworkManager::saveNetDev(const NetDevConfig &in, const std::vector<std::s cfg.dhcp = false; cfg.address.clear(); cfg.gateway.clear(); cfg.address6.clear(); cfg.gateway6.clear(); cfg.ipv6AcceptRA.clear(); cfg.dns.clear(); cfg.bond = nd.kind == "bond" ? nd.ifname : ""; cfg.bridge = nd.kind == "bridge" ? nd.ifname : ""; Loading Loading @@ -1246,7 +1275,8 @@ bool NetworkManager::writeNetDevFile(const NetDevConfig &nd, std::string *err) { // else in it): once its last VLAN is gone, it goes too static bool onlyCarriedVlans(const NetworkConfig &cfg) { return cfg.vlans.empty() && cfg.name == "10-" + cfg.matchName && !cfg.dhcp && cfg.address.empty() && cfg.gateway.empty() && cfg.dns.empty() && cfg.bond.empty() && cfg.bridge.empty(); cfg.gateway.empty() && cfg.address6.empty() && cfg.gateway6.empty() && cfg.ipv6AcceptRA.empty() && cfg.dns.empty() && cfg.bond.empty() && cfg.bridge.empty(); } // VLAN=<nd.ifname> in the own configuration of nd.parent (10-<parent>: a Loading Loading @@ -1310,7 +1340,7 @@ bool NetworkManager::deleteNetDev(const std::string &ifname, std::string *err) { 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 bool giveBack = members.size() == 1 && own && hasAddresses(*own); const NetworkConfig ownCopy = own ? *own : NetworkConfig{}; std::error_code ec; Loading Loading @@ -1697,16 +1727,20 @@ std::vector<std::string> NetworkManager::listAddresses(const std::string &ifname return out; } void NetworkManager::flushIpv4Addresses(const std::string &ifname) { void NetworkManager::flushAddresses(const std::string &ifname) { int idx = getIfIndex(ifname); if (idx < 0) return; struct Addr { struct ifaddrmsg ifa; uint32_t local, address; }; struct Addr { struct ifaddrmsg ifa; uint8_t local[16], address[16]; }; std::vector<Addr> addrs; forEachAddress(idx, [&](const struct ifaddrmsg &ifa, const void *local, const void *address) { if (ifa.ifa_family != AF_INET || !local) return; Addr a{ifa, 0, 0}; std::memcpy(&a.local, local, 4); std::memcpy(&a.address, address ? address : local, 4); // IPv6: the global ones (static, SLAAC); the link-local one belongs // to the link if (!local || (ifa.ifa_family != AF_INET && (ifa.ifa_family != AF_INET6 || ifa.ifa_scope != RT_SCOPE_UNIVERSE))) return; Addr a{ifa, {0}, {0}}; const size_t len = ifa.ifa_family == AF_INET ? 4 : 16; std::memcpy(a.local, local, len); std::memcpy(a.address, address ? address : local, len); addrs.push_back(a); }); for (auto &a : addrs) { Loading @@ -1719,13 +1753,31 @@ void NetworkManager::flushIpv4Addresses(const std::string &ifname) { msg.hdr.nlmsg_seq = 1; auto *ifa = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(&msg.hdr)); *ifa = a.ifa; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, &a.local, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, &a.address, 4); const int len = a.ifa.ifa_family == AF_INET ? 4 : 16; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, a.local, len); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, a.address, len); netlinkSend(fd, &msg.hdr); close(fd); } } // ─── IPv6 sysctls ─────────────────────────────────────────────────────────── void NetworkManager::setIpv6Sysctls(const std::string &ifname, const NetworkConfig &cfg) { if (cfg.ipv6AcceptRA.empty()) return; const std::string conf = "/proc/sys/net/ipv6/conf/" + ifname + "/"; auto set = [&](const char *key, const char *value) { int fd = ::open((conf + key).c_str(), O_WRONLY | O_CLOEXEC); bool ok = fd >= 0 && ::write(fd, value, std::strlen(value)) == static_cast<ssize_t>(std::strlen(value)); if (fd >= 0) ::close(fd); if (!ok) std::cerr << "[syslite/net] " << ifname << ": cannot set " << conf << key << std::endl; }; // 2: also with forwarding on (a host with a bridge for containers) const bool on = cfg.ipv6AcceptRA == "yes"; set("accept_ra", on ? "2" : "0"); set("autoconf", on ? "1" : "0"); } // ─── Netlink: set link up ─────────────────────────────────────────────────── bool NetworkManager::setLinkUp(const std::string &ifname) { Loading Loading @@ -1782,15 +1834,17 @@ bool NetworkManager::addAddress(const std::string &ifname, const std::string &ad int idx = getIfIndex(ifname); if (idx < 0) return false; // Parse "192.168.1.10/24" // Parse "192.168.1.10/24" or "2001:db8::10/64" auto slash = addrCidr.find('/'); if (slash == std::string::npos) return false; std::string ipStr = addrCidr.substr(0, slash); int prefixLen = std::atoi(addrCidr.substr(slash + 1).c_str()); uint32_t ip = parseIpv4(ipStr); if (ip == 0 && ipStr != "0.0.0.0") return false; const bool v6 = ipStr.find(':') != std::string::npos; uint8_t ip[16] = {0}; if (inet_pton(v6 ? AF_INET6 : AF_INET, ipStr.c_str(), ip) != 1) return false; if (prefixLen < 0 || prefixLen > (v6 ? 128 : 32)) return false; int fd = netlinkOpen(); if (fd < 0) return false; Loading @@ -1802,13 +1856,13 @@ bool NetworkManager::addAddress(const std::string &ifname, const std::string &ad msg.hdr.nlmsg_seq = 1; auto *ifa = reinterpret_cast<struct ifaddrmsg *>(NLMSG_DATA(&msg.hdr)); ifa->ifa_family = AF_INET; ifa->ifa_family = v6 ? AF_INET6 : AF_INET; ifa->ifa_prefixlen = prefixLen; ifa->ifa_index = idx; ifa->ifa_scope = RT_SCOPE_UNIVERSE; nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, &ip, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, &ip, 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_LOCAL, ip, v6 ? 16 : 4); nlAddAttr(&msg.hdr, sizeof(msg), IFA_ADDRESS, ip, v6 ? 16 : 4); bool ok = netlinkSend(fd, &msg.hdr); close(fd); Loading Loading @@ -1857,8 +1911,11 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate int idx = getIfIndex(ifname); if (idx < 0) return false; uint32_t gw = parseIpv4(gateway); if (gw == 0) return false; // IPv4 or IPv6 (a link-local one too: the route names the interface) const bool v6 = gateway.find(':') != std::string::npos; uint8_t gw[16] = {0}; if (inet_pton(v6 ? AF_INET6 : AF_INET, gateway.c_str(), gw) != 1) return false; if (!v6 && parseIpv4(gateway) == 0) return false; int fd = netlinkOpen(); if (fd < 0) return false; Loading @@ -1870,7 +1927,7 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate msg.hdr.nlmsg_seq = 1; auto *rtm = reinterpret_cast<struct rtmsg *>(NLMSG_DATA(&msg.hdr)); rtm->rtm_family = AF_INET; rtm->rtm_family = v6 ? AF_INET6 : AF_INET; rtm->rtm_dst_len = 0; // default route = /0 rtm->rtm_src_len = 0; rtm->rtm_table = RT_TABLE_MAIN; Loading @@ -1878,7 +1935,7 @@ bool NetworkManager::addRoute(const std::string &ifname, const std::string &gate rtm->rtm_scope = RT_SCOPE_UNIVERSE; rtm->rtm_type = RTN_UNICAST; nlAddAttr(&msg.hdr, sizeof(msg), RTA_GATEWAY, &gw, 4); nlAddAttr(&msg.hdr, sizeof(msg), RTA_GATEWAY, gw, v6 ? 16 : 4); int oifIdx = idx; nlAddAttr(&msg.hdr, sizeof(msg), RTA_OIF, &oifIdx, 4); Loading
src/network.h +13 −3 Changes for src/network.h: 13 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -20,6 +20,13 @@ namespace syslite { bool dhcp = false; // DHCP=yes/ipv4 std::string address; // Address=192.168.1.10/24 std::string gateway; // Gateway=192.168.1.1 // Address=/Gateway= once more for IPv6 (told apart by the ':') std::string address6; // Address=2001:db8::10/64 std::string gateway6; // Gateway=fe80::1 (link-local or global) // IPv6AcceptRA=yes|no: router advertisements (default routes and // SLAAC addresses), also with forwarding on; "": the kernel's // default (accept_ra and autoconf as they are) std::string ipv6AcceptRA; std::vector<std::string> dns; // DNS=8.8.8.8 // [Network] Bond=/Bridge=: the interface is a member of that bond or Loading Loading @@ -258,12 +265,15 @@ namespace syslite { // Apply cfg to one interface (link up, DHCP or static address/route, // DNS servers); records it as configured void configureInterface(const std::string &ifname, const NetworkConfig &cfg); // Undo it: delete its IPv4 addresses (and with them its routes), drop // Undo it: delete its addresses (and with them its routes), drop // its DNS servers void unconfigureInterface(const std::string &ifname); // Netlink: the interface's addresses; delete its IPv4 addresses // Netlink: the interface's addresses; delete its IPv4 and global // IPv6 addresses (the IPv6 link-local one stays) std::vector<std::string> listAddresses(const std::string &ifname); void flushIpv4Addresses(const std::string &ifname); void flushAddresses(const std::string &ifname); // the IPv6 sysctls of cfg (IPv6AcceptRA=): before the link goes up void setIpv6Sysctls(const std::string &ifname, const NetworkConfig &cfg); // Helper: get interface index int getIfIndex(const std::string &ifname); Loading
src/sysbus/client.cpp +6 −0 Changes for src/sysbus/client.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -71,6 +71,9 @@ InterfaceInfo interfaceFromFields(const std::vector<std::pair<std::string, std:: else if (k == "dhcp") i.dhcp = v == "yes"; else if (k == "config_address") i.configAddress = v; else if (k == "gateway") i.gateway = v; else if (k == "config_address6") i.configAddress6 = v; else if (k == "gateway6") i.gateway6 = v; else if (k == "ipv6_accept_ra") i.ipv6AcceptRA = v; else if (k == "dns") i.dns.push_back(v); else if (k == "kind") i.kind = v; else if (k == "master") i.master = v; Loading Loading @@ -318,6 +321,9 @@ static std::vector<std::pair<std::string, std::string>> settingsArgs(const Inter if (s.dhcp) a.emplace_back("dhcp", *s.dhcp ? "yes" : "no"); if (s.address) a.emplace_back("address", *s.address); if (s.gateway) a.emplace_back("gateway", *s.gateway); if (s.address6) a.emplace_back("address6", *s.address6); if (s.gateway6) a.emplace_back("gateway6", *s.gateway6); if (s.ipv6AcceptRA) a.emplace_back("ipv6_accept_ra", *s.ipv6AcceptRA); if (s.dns) { a.emplace_back("set_dns", "yes"); // also when the list is empty: clears it for (auto &d : *s.dns) a.emplace_back("dns", d); Loading
src/syslite.cpp +36 −1 Changes for src/syslite.cpp: 36 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -10,8 +10,10 @@ #include <filesystem> #include <cstring> #include <csignal> #include <cstdlib> #include <unistd.h> #include <arpa/inet.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/mount.h> Loading Loading @@ -379,6 +381,31 @@ namespace syslite { return r; } // what is wrong with cfg's addresses ("" if nothing): Address= and // Gateway= per address family, IPv6AcceptRA= yes, no or unset static std::string checkAddresses(const NetworkConfig &cfg) { auto ip = [](int fam, const std::string &s) { unsigned char buf[16]; return inet_pton(fam, s.c_str(), buf) == 1; }; auto cidr = [&](int fam, const std::string &s) { size_t slash = s.find('/'); if (slash == std::string::npos || !ip(fam, s.substr(0, slash))) return false; std::string len = s.substr(slash + 1); if (len.empty() || len.size() > 3 || len.find_first_not_of("0123456789") != std::string::npos) return false; return std::atoi(len.c_str()) <= (fam == AF_INET ? 32 : 128); }; if (!cfg.address.empty() && !cidr(AF_INET, cfg.address)) return "invalid address '" + cfg.address + "' (a.b.c.d/len)"; if (!cfg.gateway.empty() && !ip(AF_INET, cfg.gateway)) return "invalid gateway '" + cfg.gateway + "'"; if (!cfg.address6.empty() && !cidr(AF_INET6, cfg.address6)) return "invalid IPv6 address '" + cfg.address6 + "' (2001:db8::10/64)"; if (!cfg.gateway6.empty() && !ip(AF_INET6, cfg.gateway6)) return "invalid IPv6 gateway '" + cfg.gateway6 + "'"; if (!cfg.ipv6AcceptRA.empty() && cfg.ipv6AcceptRA != "yes" && cfg.ipv6AcceptRA != "no") return "ipv6_accept_ra takes yes, no or nothing (the kernel's default)"; return ""; } static void addInterfaceFields(sysbus::Message &m, const InterfaceStatus &st) { m.add("interface", st.name); m.add("mac", st.mac); Loading @@ -397,6 +424,9 @@ namespace syslite { m.add("dhcp", cfg->dhcp ? "yes" : "no"); m.add("config_address", cfg->address); m.add("gateway", cfg->gateway); m.add("config_address6", cfg->address6); m.add("gateway6", cfg->gateway6); m.add("ipv6_accept_ra", cfg->ipv6AcceptRA); for (auto &d : cfg->dns) m.add("dns", d); if (!cfg->bond.empty()) m.add("member_of", cfg->bond); else if (!cfg->bridge.empty()) m.add("member_of", cfg->bridge); Loading Loading @@ -478,12 +508,17 @@ namespace syslite { if (call.has("dhcp")) cfg.dhcp = call.get("dhcp") == "yes"; if (call.has("address")) cfg.address = call.get("address"); if (call.has("gateway")) cfg.gateway = call.get("gateway"); if (call.has("address6")) cfg.address6 = call.get("address6"); if (call.has("gateway6")) cfg.gateway6 = call.get("gateway6"); if (call.has("ipv6_accept_ra")) cfg.ipv6AcceptRA = call.get("ipv6_accept_ra"); if (call.has("set_dns")) cfg.dns = call.getAll("dns"); if (cfg.matchName.empty()) return Message::error(call, err::InvalidArgs, "a configuration needs a match (interface name or pattern)"); if (std::string bad = checkAddresses(cfg); !bad.empty()) return Message::error(call, err::InvalidArgs, bad); // without addresses only what carries others: a VLAN parent, a // bond/bridge/VLAN (layer 2 only, e.g. for containers) if (!cfg.dhcp && cfg.address.empty() && cfg.bond.empty() && cfg.bridge.empty() && cfg.vlans.empty() && if (!cfg.dhcp && cfg.address.empty() && cfg.address6.empty() && cfg.ipv6AcceptRA != "yes" && cfg.bond.empty() && cfg.bridge.empty() && cfg.vlans.empty() && !_network.findNetDev(cfg.matchName)) return Message::error(call, err::InvalidArgs, "a configuration without DHCP needs an address"); return done(_network.saveConfig(cfg, &why), {}); Loading