diff --git a/.gitignore b/.gitignore index 48d2244..6e2bb95 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ deps/tools/rpiConfigServer deps/tools/setuidgids *.o *.sw? +*.debug stage2/01-sys-tweaks/extfiles diff --git a/deps/tools/Makefile b/deps/tools/Makefile index 9fe2696..7d3c9ab 100644 --- a/deps/tools/Makefile +++ b/deps/tools/Makefile @@ -33,6 +33,7 @@ _cscore.so: ../robotpy-cscore/src/_cscore.cpp ../robotpy-cscore/src/ndarray_conv RPICONFIGSERVER_OBJS= \ rpiConfigServer_src/main.o \ rpiConfigServer_src/MyHttpConnection.o \ + rpiConfigServer_src/NetworkSettings.o \ rpiConfigServer_src/SystemStatus.o \ rpiConfigServer_src/VisionStatus.o \ rpiConfigServer_src/WebSocketHandlers.o \ @@ -45,6 +46,8 @@ rpiConfigServer: ${RPICONFIGSERVER_OBJS} ${RPICONFIGSERVER_OBJS} \ -L../allwpilib/wpiutil/build/libs/wpiutil/static/release \ -lwpiutil + ${COMPILER}objcopy --only-keep-debug $@ $@.debug + ${COMPILER}strip -g $@ %.o: %.cpp ${COMPILER}g++ -O -Wall -c -o $@ \ diff --git a/deps/tools/rpiConfigServer_src/NetworkSettings.cpp b/deps/tools/rpiConfigServer_src/NetworkSettings.cpp new file mode 100644 index 0000000..2519d8b --- /dev/null +++ b/deps/tools/rpiConfigServer_src/NetworkSettings.cpp @@ -0,0 +1,232 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "NetworkSettings.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace uv = wpi::uv; + +#define DHCPCD_CONF "/boot/dhcpcd.conf" + +#define GEN_MARKER "###### BELOW THIS LINE EDITED BY RPICONFIGSERVER ######" + +/* + Format of generated portion for static: + + interface eth0 + static ip_address=/ + static routers= + static domain_name_servers= + + For static fallback: + + profile static_eth0 + static ip_address=/ + static routers= + static domain_name_servers= + interface eth0 + fallback static_eth0 + */ + +wpi::StringRef CidrToNetmask(unsigned int cidr, + wpi::SmallVectorImpl& buf) { + in_addr addr = { htonl(wpi::maskLeadingOnes(cidr)) }; + wpi::uv::AddrToName(addr, &buf); + return wpi::StringRef(buf.data(), buf.size()); +} + +bool NetmaskToCidr(wpi::StringRef netmask, unsigned int* cidr) { + in_addr addr; + if (wpi::uv::NameToAddr(netmask, &addr) != 0) return false; + uint32_t hostAddr = ntohl(addr.s_addr); + auto leadingOnes = wpi::countLeadingOnes(hostAddr); + auto trailingZeros = wpi::countTrailingZeros(hostAddr); + if (leadingOnes + trailingZeros != 32) return false; + *cidr = leadingOnes; + return true; +} + +std::shared_ptr NetworkSettings::GetInstance() { + static auto inst = std::make_shared(private_init{}); + return inst; +} + +void NetworkSettings::Set(Mode mode, wpi::StringRef address, + wpi::StringRef mask, wpi::StringRef gateway, + wpi::StringRef dns, + std::function onFail) { + // validate and sanitize inputs + wpi::SmallString<32> addressOut; + unsigned int cidr; + wpi::SmallString<32> gatewayOut; + wpi::SmallString<128> dnsOut; + + // address + in_addr addressAddr; + if (wpi::uv::NameToAddr(address, &addressAddr) != 0) { + wpi::SmallString<128> err; + err += "invalid address '"; + err += address; + err += "'"; + onFail(err); + return; + } + wpi::uv::AddrToName(addressAddr, &addressOut); + + // mask + if (!NetmaskToCidr(mask, &cidr)) { + wpi::SmallString<128> err; + err += "invalid netmask '"; + err += mask; + err += "'"; + onFail(err); + return; + } + + // gateway (may be blank) + in_addr gatewayAddr; + if (wpi::uv::NameToAddr(gateway, &gatewayAddr) == 0) + wpi::uv::AddrToName(gatewayAddr, &gatewayOut); + + // dns + wpi::SmallVector dnsStrs; + wpi::SmallString<32> oneDnsOut; + bool first = true; + dns.split(dnsStrs, ' ', -1, false); + for (auto dnsStr : dnsStrs) { + in_addr dnsAddr; + if (wpi::uv::NameToAddr(dnsStr, &dnsAddr) != 0) { + wpi::SmallString<128> err; + err += "invalid DNS address '"; + err += dnsStr; + err += "'"; + onFail(err); + return; + } + wpi::uv::AddrToName(dnsAddr, &oneDnsOut); + if (!first) dnsOut += ' '; + first = false; + dnsOut += oneDnsOut; + } + + // read file (up to but not including the marker) + std::vector lines; + std::error_code ec; + { + wpi::raw_fd_istream is(DHCPCD_CONF, ec); + if (ec) { + onFail("could not read " DHCPCD_CONF); + return; + } + + wpi::SmallString<256> lineBuf; + while (!is.has_error()) { + wpi::StringRef line = is.getline(lineBuf, 256).trim(); + if (line == GEN_MARKER) break; + lines.emplace_back(line); + } + } + + // write file + { + // write original lines + wpi::raw_fd_ostream os(DHCPCD_CONF, ec, wpi::sys::fs::F_Text); + if (ec) { + onFail("could not write " DHCPCD_CONF); + return; + } + for (auto&& line : lines) + os << line << '\n'; + + // write marker + os << GEN_MARKER << '\n'; + + // write generated config + switch (mode) { + case kDhcp: + break; // nothing required + case kStatic: + os << "interface eth0\n"; + os << "static ip_address=" << addressOut << '/' << cidr << '\n'; + if (!gatewayOut.empty()) os << "static routers=" << gatewayOut << '\n'; + if (!dnsOut.empty()) + os << "static domain_name_servers=" << dnsOut << '\n'; + break; + case kDhcpStatic: + os << "profile static_eth0\n"; + os << "static ip_address=" << addressOut << '/' << cidr << '\n'; + if (!gatewayOut.empty()) os << "static routers=" << gatewayOut << '\n'; + if (!dnsOut.empty()) + os << "static domain_name_servers=" << dnsOut << '\n'; + os << "interface eth0\n"; + os << "fallback static_eth0\n"; + break; + } + } + + // tell dhcpcd to reload config + if (auto proc = + uv::Process::Spawn(m_loop, "/sbin/dhcpcd", "/sbin/dhcpcd", "-n")) { + proc->exited.connect([p = proc.get()](int64_t, int) { p->Close(); }); + } + + UpdateStatus(); +} + +void NetworkSettings::UpdateStatus() { status(GetStatusJson()); } + +wpi::json NetworkSettings::GetStatusJson() { + std::error_code ec; + wpi::raw_fd_istream is(DHCPCD_CONF, ec); + if (ec) { + wpi::errs() << "could not read " DHCPCD_CONF "\n"; + return wpi::json(); + } + + wpi::json j = {{"type", "networkSettings"}, {"networkApproach", "dhcp"}}; + + wpi::SmallString<256> lineBuf; + bool foundMarker = false; + while (!is.has_error()) { + wpi::StringRef line = is.getline(lineBuf, 256).trim(); + if (line == GEN_MARKER) foundMarker = true; + if (!foundMarker) continue; + if (line.empty()) continue; + if (line.startswith("static ip_address")) { + j["networkApproach"] = "static"; + + wpi::StringRef value = line.split('=').second.trim(); + wpi::StringRef cidrStr; + std::tie(j["networkAddress"], cidrStr) = value.split('/'); + + unsigned int cidrInt; + if (!cidrStr.getAsInteger(10, cidrInt)) { + wpi::SmallString<64> netmaskBuf; + j["networkMask"] = CidrToNetmask(cidrInt, netmaskBuf); + } + } else if (line.startswith("static routers")) { + j["networkGateway"] = line.split('=').second.trim(); + } else if (line.startswith("static domain_name_servers")) { + j["networkDNS"] = line.split('=').second.trim(); + } else if (line.startswith("fallback")) { + j["networkApproach"] = "dhcp-fallback"; + } + } + + return j; +} diff --git a/deps/tools/rpiConfigServer_src/NetworkSettings.h b/deps/tools/rpiConfigServer_src/NetworkSettings.h new file mode 100644 index 0000000..d8f13a8 --- /dev/null +++ b/deps/tools/rpiConfigServer_src/NetworkSettings.h @@ -0,0 +1,52 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef RPICONFIGSERVER_NETWORKSETTINGS_H_ +#define RPICONFIGSERVER_NETWORKSETTINGS_H_ + +#include +#include + +#include +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +class NetworkSettings { + struct private_init {}; + + public: + explicit NetworkSettings(const private_init&) {} + NetworkSettings(const NetworkSettings&) = delete; + NetworkSettings& operator=(const NetworkSettings&) = delete; + + void SetLoop(std::shared_ptr loop) { + m_loop = std::move(loop); + } + + enum Mode { kDhcp, kStatic, kDhcpStatic }; + + void Set(Mode mode, wpi::StringRef address, wpi::StringRef mask, + wpi::StringRef gateway, wpi::StringRef dns, + std::function onFail); + + void UpdateStatus(); + + wpi::json GetStatusJson(); + + wpi::sig::Signal status; + + static std::shared_ptr GetInstance(); + + private: + std::shared_ptr m_loop; +}; + +#endif // RPICONFIGSERVER_NETWORKSETTINGS_H_ diff --git a/deps/tools/rpiConfigServer_src/SystemStatus.cpp b/deps/tools/rpiConfigServer_src/SystemStatus.cpp index eb4fda9..a2bf261 100644 --- a/deps/tools/rpiConfigServer_src/SystemStatus.cpp +++ b/deps/tools/rpiConfigServer_src/SystemStatus.cpp @@ -105,7 +105,7 @@ bool SystemStatus::GetWritable() { line.split(strs, ' ', -1, false); if (strs.size() < 4) continue; - if (strs[1] == "/") return strs[2].contains("rw"); + if (strs[1] == "/") return strs[3].contains("rw"); } return false; } diff --git a/deps/tools/rpiConfigServer_src/VisionStatus.h b/deps/tools/rpiConfigServer_src/VisionStatus.h index 8f6f146..2811681 100644 --- a/deps/tools/rpiConfigServer_src/VisionStatus.h +++ b/deps/tools/rpiConfigServer_src/VisionStatus.h @@ -13,13 +13,13 @@ #include #include +#include namespace wpi { class json; namespace uv { class Buffer; -class Loop; } // namespace uv } // namespace wpi diff --git a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp index a7acece..c672511 100644 --- a/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp +++ b/deps/tools/rpiConfigServer_src/WebSocketHandlers.cpp @@ -18,6 +18,7 @@ #include #include +#include "NetworkSettings.h" #include "SystemStatus.h" #include "VisionStatus.h" @@ -32,6 +33,7 @@ struct WebSocketData { wpi::sig::ScopedConnection sysWritableConn; wpi::sig::ScopedConnection visStatusConn; wpi::sig::ScopedConnection visLogConn; + wpi::sig::ScopedConnection netSettingsConn; }; static void SendWsText(wpi::WebSocket& ws, const wpi::json& j) { @@ -118,6 +120,13 @@ void InitWs(wpi::WebSocket& ws) { if (d->visionLogEnabled) SendWsText(ws, j); }); visStatus->UpdateStatus(); + + // send initial network settings + auto netSettings = NetworkSettings::GetInstance(); + auto netSettingsFunc = [&ws](const wpi::json& j) { SendWsText(ws, j); }; + netSettingsFunc(netSettings->GetStatusJson()); + data->netSettingsConn = + netSettings->status.connect_connection(netSettingsFunc); } void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { @@ -200,5 +209,31 @@ void ProcessWsText(wpi::WebSocket& ws, wpi::StringRef msg) { } } } else if (t == "networkSave") { + auto statusFunc = [s = ws.shared_from_this()](wpi::StringRef msg) { + SendWsText(*s, {{"type", "status"}, {"message", msg}}); + }; + try { + NetworkSettings::Mode mode; + auto& approach = j.at("networkApproach").get_ref(); + if (approach == "dhcp") + mode = NetworkSettings::kDhcp; + else if (approach == "static") + mode = NetworkSettings::kStatic; + else if (approach == "dhcp-fallback") + mode = NetworkSettings::kDhcpStatic; + else { + wpi::errs() << "could not understand networkApproach value: " + << approach << '\n'; + return; + } + NetworkSettings::GetInstance()->Set( + mode, j.at("networkAddress").get_ref(), + j.at("networkMask").get_ref(), + j.at("networkGateway").get_ref(), + j.at("networkDNS").get_ref(), statusFunc); + } catch (const wpi::json::exception& e) { + wpi::errs() << "could not read networkSave value: " << e.what() << '\n'; + return; + } } } diff --git a/deps/tools/rpiConfigServer_src/main.cpp b/deps/tools/rpiConfigServer_src/main.cpp index 53b778f..0eb4071 100644 --- a/deps/tools/rpiConfigServer_src/main.cpp +++ b/deps/tools/rpiConfigServer_src/main.cpp @@ -20,6 +20,7 @@ #include #include "MyHttpConnection.h" +#include "NetworkSettings.h" #include "SystemStatus.h" #include "VisionStatus.h" @@ -37,6 +38,7 @@ int main(int argc, char* argv[]) { auto loop = uv::Loop::Create(); + NetworkSettings::GetInstance()->SetLoop(loop); VisionStatus::GetInstance()->SetLoop(loop); loop->error.connect( diff --git a/deps/tools/rpiConfigServer_src/resources/frcvision.js b/deps/tools/rpiConfigServer_src/resources/frcvision.js index b5456db..f1cee39 100644 --- a/deps/tools/rpiConfigServer_src/resources/frcvision.js +++ b/deps/tools/rpiConfigServer_src/resources/frcvision.js @@ -153,11 +153,12 @@ function connect() { visionLog(msg.data); break; case 'networkSettings': - $('#networkApproach').value = msg.networkApproach; - $('#networkAddress').value = msg.networkAddress; - $('#networkMask').value = msg.networkMask; - $('#networkGateway').value = msg.networkGateway; - $('#networkDNS').value = msg.networkDNS; + $('#networkApproach').val(msg.networkApproach); + $('#networkAddress').val(msg.networkAddress); + $('#networkMask').val(msg.networkMask); + $('#networkGateway').val(msg.networkGateway); + $('#networkDNS').val(msg.networkDNS); + updateNetworkSettingsView(); break; case 'systemReadOnly': displayReadOnly(); @@ -268,25 +269,27 @@ function visionLog(data) { } // Show details when appropriate for network approach -$('#networkApproach').change(function() { - if (this.value == "dhcp") { +function updateNetworkSettingsView() { + if ($('#networkApproach').val() === "dhcp") { $('#networkIpDetails').collapse('hide'); } else { $('#networkIpDetails').collapse('show'); } +} + +$('#networkApproach').change(function() { + updateNetworkSettingsView(); }); // Network Save button handler $('#networkSave').click(function() { - var $this = $(this); - $this.button('loading'); var msg = { type: 'networkSave', - networkApproach: $('#networkApproach').value, - networkAddress: $('#networkAddress').value, - networkMask: $('#networkMask').value, - networkGateway: $('#networkGateway').value, - networkDNS: $('#networkDNS').value + networkApproach: $('#networkApproach').val(), + networkAddress: $('#networkAddress').val(), + networkMask: $('#networkMask').val(), + networkGateway: $('#networkGateway').val(), + networkDNS: $('#networkDNS').val() }; connection.send(JSON.stringify(msg)); }); diff --git a/deps/tools/rpiConfigServer_src/resources/index.html b/deps/tools/rpiConfigServer_src/resources/index.html index 5a894af..3984a54 100644 --- a/deps/tools/rpiConfigServer_src/resources/index.html +++ b/deps/tools/rpiConfigServer_src/resources/index.html @@ -197,7 +197,7 @@ - @@ -405,11 +405,11 @@
- -