From b0bb0b76095eb5265f5e5254a365039652fe158c Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 21 Aug 2020 15:07:37 +0000 Subject: [PATCH] initial route poking --- llarp/CMakeLists.txt | 1 + llarp/handlers/null.hpp | 6 + llarp/handlers/tun.cpp | 7 + llarp/handlers/tun.hpp | 4 + llarp/net/ip_address.hpp | 6 +- llarp/net/route.cpp | 229 ++++++++++++++++++++++++++++ llarp/net/route.hpp | 28 ++++ llarp/router/router.cpp | 5 + llarp/rpc/rpc_server.cpp | 43 ++++++ llarp/service/endpoint.hpp | 3 + pybind/llarp/handlers/pyhandler.hpp | 6 + 11 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 llarp/net/route.cpp create mode 100644 llarp/net/route.hpp diff --git a/llarp/CMakeLists.txt b/llarp/CMakeLists.txt index 02ace3898..42d5a5983 100644 --- a/llarp/CMakeLists.txt +++ b/llarp/CMakeLists.txt @@ -59,6 +59,7 @@ add_library(lokinet-platform net/ip_range.cpp net/net.cpp net/net_int.cpp + net/route.cpp net/sock_addr.cpp $ ) diff --git a/llarp/handlers/null.hpp b/llarp/handlers/null.hpp index cdbba4c9b..4ace25177 100644 --- a/llarp/handlers/null.hpp +++ b/llarp/handlers/null.hpp @@ -22,6 +22,12 @@ namespace llarp return true; } + std::string + GetIfName() const override + { + return ""; + } + path::PathSet_ptr GetSelf() override { diff --git a/llarp/handlers/tun.cpp b/llarp/handlers/tun.cpp index e291e8775..5411816a1 100644 --- a/llarp/handlers/tun.cpp +++ b/llarp/handlers/tun.cpp @@ -549,6 +549,12 @@ namespace llarp return true; } + std::string + TunEndpoint::GetIfName() const + { + return m_IfName; + } + bool TunEndpoint::Start() { @@ -661,6 +667,7 @@ namespace llarp ifaddr = vpn.info.ifaddr; netmask = vpn.info.netmask; } + m_IfName = ifname; if (ip.FromString(ifaddr)) { m_OurIP = net::ExpandV4(ip); diff --git a/llarp/handlers/tun.hpp b/llarp/handlers/tun.hpp index 81f84feec..d8a1bbe1d 100644 --- a/llarp/handlers/tun.hpp +++ b/llarp/handlers/tun.hpp @@ -36,6 +36,9 @@ namespace llarp void SendPacketToRemote(const llarp_buffer_t&) override{}; + std::string + GetIfName() const override; + void Tick(llarp_time_t now) override; @@ -270,6 +273,7 @@ namespace llarp llarp_vpn_io* io; }; std::promise m_LazyVPNPromise; + std::string m_IfName; /// send packets on endpoint to user using send function /// send function returns true to indicate stop iteration and do codel diff --git a/llarp/net/ip_address.hpp b/llarp/net/ip_address.hpp index 2315dc798..9a468734d 100644 --- a/llarp/net/ip_address.hpp +++ b/llarp/net/ip_address.hpp @@ -118,9 +118,11 @@ namespace llarp std::string toString() const; - /// get ip address component std::string - getIpAddr() const; + toHost() const + { + return m_ipAddress; + } // TODO: other utility functions left over from Addr which may be useful // IsBogon() const; diff --git a/llarp/net/route.cpp b/llarp/net/route.cpp new file mode 100644 index 000000000..bf4f9494c --- /dev/null +++ b/llarp/net/route.cpp @@ -0,0 +1,229 @@ +#include "route.hpp" + +namespace llarp::net +{ +#ifndef __linux__ + void + Execute(std::string cmd) + { + std::cout << cmd << std::endl; +#ifdef _WIN32 + system(cmd.c_str()); +#else + std::vector parts_str; + std::vector parts_raw; + std::stringstream in(cmd); + for (std::string part; std::getline(in, part, ' ');) + { + if (part.empty()) + continue; + parts_str.push_back(part); + } + for (const auto& part : parts_str) + { + parts_raw.push_back(part.c_str()); + } + parts_raw.push_back(nullptr); + const auto pid = fork(); + if (pid == -1) + { + throw std::runtime_error("failed to fork"); + } + else if (pid == 0) + { + char* const* args = (char* const*)parts_raw.data(); + const auto result = execv(parts_raw[0], args); + if (result) + { + std::cout << "failed: " << result << std::endl; + } + else + { + std::cout << "ok" << std::endl; + } + exit(result); + } + else + { + waitpid(pid, 0, 0); + } +#endif + } +#endif + + void + AddRoute(std::string ip, std::string gateway) + { +#ifdef __linux__ +#else + std::stringstream ss; +#if _WIN32 + ss << "route ADD " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2"; +#elif __APPLE__ + ss << "route -n add -host " << ip << " " << gateway; +#else +#error unsupported platform +#endif + Execute(ss.str()); +#endif + } + + void + DelRoute(std::string ip, std::string gateway) + { +#ifdef __linux__ +#else + std::stringstream ss; + +#if _WIN32 + ss << "route DELETE " << ip << " MASK 255.255.255.255 " << gateway << " METRIC 2"; +#elif __APPLE__ + ss << "route -n delete -host " << ip << " " << gateway; +#else +#error unsupported platform +#endif + Execute(ss.str()); +#endif + } + + void + AddDefaultRouteViaInterface(std::string ifname) + { +#ifdef __linux__ + // Execute("/sbin/ip route add default dev " + ifname); +#elif _WIN32 + ifname.back()++; + Execute("route ADD 0.0.0.0 MASK 128.0.0.0 " + ifname); + Execute("route ADD 128.0.0.0 MASK 128.0.0.0 " + ifname); +#elif __APPLE__ + Execute("route -cloning add -net 0.0.0.0 -netmask 0.0.0.0 -interface " + ifname); +#else +#error unsupported platform +#endif + } + + void + DelDefaultRouteViaInterface(std::string ifname) + { +#ifdef __linux__ + // Execute("/sbin/ip route del default dev " + ifname); +#elif _WIN32 + ifname.back()++; + Execute("route DELETE 0.0.0.0 MASK 128.0.0.0 " + ifname); + Execute("route DELETE 128.0.0.0 MASK 128.0.0.0 " + ifname); +#elif __APPLE__ + Execute("route -cloning delete -net 0.0.0.0 -netmask 0.0.0.0 -interface " + ifname); +#else +#error unsupported platform +#endif + } + + std::vector + GetGatewaysNotOnInterface(std::string ifname) + { + std::vector gateways; +#ifdef __linux__ + /* + FILE* p = popen("ip route", "r"); + if (p == nullptr) + return gateways; + char* line = nullptr; + size_t len = 0; + ssize_t read = 0; + while ((read = getline(&line, &len, p)) != -1) + { + std::string line_str(line, len); + std::vector words; + std::istringstream instr(line_str); + for (std::string word; std::getline(instr, word, ' ');) + { + words.emplace_back(std::move(word)); + } + if (words[0] == "default" and words[1] == "via" and words[3] == "dev" and words[4] != ifname) + { + gateways.emplace_back(std::move(words[2])); + } + } + pclose(p); + */ + return gateways; +#elif _WIN32 +#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x)) +#define FREE(x) HeapFree(GetProcessHeap(), 0, (x)) + PMIB_IPFORWARDTABLE pIpForwardTable; + DWORD dwSize = 0; + DWORD dwRetVal = 0; + + pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(sizeof(MIB_IPFORWARDTABLE)); + if (pIpForwardTable == nullptr) + return gateways; + + if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) + { + FREE(pIpForwardTable); + pIpForwardTable = (MIB_IPFORWARDTABLE*)MALLOC(dwSize); + if (pIpForwardTable == nullptr) + { + return gateways; + } + } + + if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR) + { + for (int i = 0; i < (int)pIpForwardTable->dwNumEntries; i++) + { + struct in_addr gateway, interface_addr; + gateway.S_un.S_addr = (u_long)pIpForwardTable->table[i].dwForwardDest; + interface_addr.S_un.S_addr = (u_long)pIpForwardTable->table[i].dwForwardNextHop; + std::array interface_str{}; + StringCchCopy(interface_str.data(), interface_str.size(), inet_ntoa(interface_addr)); + std::string interface_name{interface_str.data()}; + if ((!gateway.S_un.S_addr) and interface_name != ifname) + { + gateways.push_back(std::move(interface_name)); + } + } + } + FREE(pIpForwardTable); +#undef MALLOC +#undef FREE + return gateways; +#elif __APPLE__ + const auto maybe = llarp::GetIFAddr(ifname); + if (not maybe.has_value()) + return gateways; + const auto interface = maybe->toString(); + // mac os is so godawful man + FILE* p = popen("netstat -rn -f inet", "r"); + if (p == nullptr) + return gateways; + char* line = nullptr; + size_t len = 0; + ssize_t read = 0; + while ((read = getline(&line, &len, p)) != -1) + { + std::string line_str(line, len); + if (line_str.find("default") == 0) + { + line_str = line_str.substr(7); + while (line_str[0] == ' ') + { + line_str = line_str.substr(1); + } + const auto pos = line_str.find(" "); + if (pos != std::string::npos) + { + auto gateway = line_str.substr(0, pos); + if (gateway != interface) + gateways.emplace_back(std::move(gateway)); + } + } + } + pclose(p); + return gateways; +#else +#error unsupported platform +#endif + } + +} // namespace llarp::net diff --git a/llarp/net/route.hpp b/llarp/net/route.hpp new file mode 100644 index 000000000..6dccbbf23 --- /dev/null +++ b/llarp/net/route.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace llarp::net +{ + /// get every ip address that is a gateway that isn't owned by interface with name ifname + std::vector + GetGatewaysNotOnInterface(std::string ifname); + + /// add route to ipaddr via gateway ip + void + AddRoute(std::string ipaddr, std::string gateway); + + /// delete route to ipaddr via gateway ip + void + DelRoute(std::string ipaddr, std::string gateway); + + /// add default route via interface with name ifname + void + AddDefaultRouteViaInterface(std::string ifname); + + /// delete default route via interface with name ifname + void + DelDefaultRouteViaInterface(std::string ifname); + +} // namespace llarp::net diff --git a/llarp/router/router.cpp b/llarp/router/router.cpp index 3ec750fc5..1ca165492 100644 --- a/llarp/router/router.cpp +++ b/llarp/router/router.cpp @@ -587,6 +587,11 @@ namespace llarp _linkManager.AddLink(std::move(server), true); } + if (conf.links.m_InboundLinks.empty() and m_isServiceNode) + { + throw std::runtime_error("service node enabled but have no inbound links"); + } + // Network config if (conf.network.m_enableProfiling.has_value() and not*conf.network.m_enableProfiling) { diff --git a/llarp/rpc/rpc_server.cpp b/llarp/rpc/rpc_server.cpp index 7a3c77f48..1a33b665f 100644 --- a/llarp/rpc/rpc_server.cpp +++ b/llarp/rpc/rpc_server.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -111,6 +112,11 @@ namespace llarp::rpc }) .add_request_command("exit", [&](lokimq::Message& msg) { HandleJSONRequest(msg, [r = m_Router](nlohmann::json obj, ReplyFunction_t reply) { + if (r->IsServiceNode()) + { + reply(CreateJSONError("not supported")); + return; + } std::optional exit; IPRange range; bool map = true; @@ -164,11 +170,35 @@ namespace llarp::rpc } if (map and exit.has_value()) { + const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName()); + if (gateways.empty()) + { + reply(CreateJSONError("no gateway found")); + return; + } ep->MapExitRange(range, *exit); if (token.has_value()) { ep->SetAuthInfoForEndpoint(*exit, service::AuthInfo{*token}); } + ep->EnsurePathToService( + *exit, + [r, gateway = gateways[0], reply, ep](auto, service::OutboundContext* ctx) { + if (ctx == nullptr) + { + reply(CreateJSONError("could not find exit")); + return; + } + r->ForEachPeer( + [gateway](const auto* link, bool) { + net::AddRoute(link->GetRemoteEndpoint().toHost(), gateway); + }, + false); + net::AddDefaultRouteViaInterface(ep->GetIfName()); + reply(CreateJSONResponse("OK")); + }, + 5s); + return; } else if (map and not exit.has_value()) { @@ -177,6 +207,19 @@ namespace llarp::rpc } else if (not map) { + const auto gateways = net::GetGatewaysNotOnInterface(ep->GetIfName()); + if (gateways.empty()) + { + reply(CreateJSONError("no gateway found")); + return; + } + net::DelDefaultRouteViaInterface(ep->GetIfName()); + r->ForEachPeer( + [gateway = gateways[0]](const auto* link, bool) { + net::DelRoute(link->GetRemoteEndpoint().toHost(), gateway); + }, + false); + ep->UnmapExitRange(range); } reply(CreateJSONResponse("OK")); diff --git a/llarp/service/endpoint.hpp b/llarp/service/endpoint.hpp index f5c1ebd28..8209277c6 100644 --- a/llarp/service/endpoint.hpp +++ b/llarp/service/endpoint.hpp @@ -108,6 +108,9 @@ namespace llarp return false; } + virtual std::string + GetIfName() const = 0; + /// inject vpn io /// return false if not supported virtual bool diff --git a/pybind/llarp/handlers/pyhandler.hpp b/pybind/llarp/handlers/pyhandler.hpp index 885668047..f33104236 100644 --- a/pybind/llarp/handlers/pyhandler.hpp +++ b/pybind/llarp/handlers/pyhandler.hpp @@ -62,6 +62,12 @@ namespace llarp return {0}; } + std::string + GetIfName() const override + { + return ""; + } + using PacketHandler_t = std::function, service::ProtocolType)>;