mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
329 lines
9 KiB
C++
329 lines
9 KiB
C++
#include"Boss/Mod/InternetConnectionMonitor.hpp"
|
|
#include"Boss/Mod/Rpc.hpp"
|
|
#include"Boss/Mod/Waiter.hpp"
|
|
#include"Boss/Msg/Init.hpp"
|
|
#include"Boss/Msg/ListpeersAnalyzedResult.hpp"
|
|
#include"Boss/Msg/NeedsConnect.hpp"
|
|
#include"Boss/Msg/TaskCompletion.hpp"
|
|
#include"Boss/concurrent.hpp"
|
|
#include"Boss/log.hpp"
|
|
#include"Boss/random_engine.hpp"
|
|
#include"Ev/Io.hpp"
|
|
#include"Ev/ThreadPool.hpp"
|
|
#include"Ev/map.hpp"
|
|
#include"Jsmn/Object.hpp"
|
|
#include"Json/Out.hpp"
|
|
#include"Net/Connector.hpp"
|
|
#include"Net/SocketFd.hpp"
|
|
#include"S/Bus.hpp"
|
|
#include"Util/make_unique.hpp"
|
|
#include<algorithm>
|
|
#include<assert.h>
|
|
#include<iterator>
|
|
#include<random>
|
|
#include<string>
|
|
|
|
namespace {
|
|
|
|
/* How long before we timeout an LN-level ping attempt
|
|
* with a peer and consider it to have failed? */
|
|
auto const ln_ping_timeout = double(30.0);
|
|
|
|
/* List of servers that we try to connect to.
|
|
* When we think we might get disconnected, we select
|
|
* one of these by random and try to connect.
|
|
* If the connection succeeds we consider ourselves
|
|
* to be connected.
|
|
* If the connection fails we try *all* of the below
|
|
* and if at least one connects, we still consider
|
|
* ourselves to be connected.
|
|
*
|
|
* Note that we connect, then disconnect immediately.
|
|
* We just want to check the Internet, not spam these
|
|
* servers.
|
|
*
|
|
* It would probably be better to "just" IP-level `ping`
|
|
* the server, but that is suprisingly complicated.
|
|
* Either we require some kind of `ping` command and
|
|
* parse its output (including figuring out what
|
|
* exactly its arguments are, its version, the exact
|
|
* variant of the `ping` command, etc.), or we need to
|
|
* open an ICMP socket, which, depending on OS policies,
|
|
* might not be possible for non-root users (`ping` is
|
|
* traditionally a setuid program).
|
|
*/
|
|
auto const servers = std::vector<std::pair<std::string, int>>
|
|
{ {"www.duckduckgo.com", 443}
|
|
, {"www.google.com", 443}
|
|
, {"www.yahoo.com", 443}
|
|
, {"www.facebook.com", 443}
|
|
, {"www.amazon.com", 443}
|
|
, {"www.reddit.com", 443}
|
|
, {"www.twitter.com", 443}
|
|
, {"www.alibaba.com", 443}
|
|
, {"www.netflix.com", 443}
|
|
, {"www.baidu.com", 443}
|
|
, {"www.paypal.com", 443}
|
|
, {"www.microsoft.com", 443}
|
|
, {"www.mozilla.org", 443}
|
|
, {"1.1.1.1", 443}
|
|
, {"www.squareup.com", 443}
|
|
, {"wikipedia.org", 443}
|
|
, {"www.bitcoin.org", 443}
|
|
, {"bitcoinstats.com", 443}
|
|
, {"www.iana.org", 443}
|
|
, {"www.example.org", 443}
|
|
, {"github.com", 443}
|
|
, {"lightning.network", 443}
|
|
, {"www.blockstream.com", 443}
|
|
, {"acinq.co", 443}
|
|
};
|
|
auto dist = std::uniform_int_distribution<std::size_t>(
|
|
0, servers.size() - 1
|
|
);
|
|
|
|
}
|
|
|
|
namespace Boss { namespace Mod {
|
|
|
|
class InternetConnectionMonitor::Impl {
|
|
private:
|
|
S::Bus& bus;
|
|
Ev::ThreadPool& threadpool;
|
|
Boss::Mod::Waiter& waiter;
|
|
|
|
Net::Connector *connector;
|
|
Boss::Mod::Rpc *rpc;
|
|
|
|
bool online;
|
|
bool checking_connectivity;
|
|
|
|
void start() {
|
|
bus.subscribe< Msg::Init
|
|
>([this](Msg::Init const& init) {
|
|
connector = &init.connector;
|
|
rpc = &init.rpc;
|
|
checking_connectivity = true;
|
|
return Boss::concurrent(server_check());
|
|
});
|
|
bus.subscribe< Msg::ListpeersAnalyzedResult
|
|
>([this](Msg::ListpeersAnalyzedResult const& l) {
|
|
if (!connector || checking_connectivity)
|
|
return Ev::lift();
|
|
checking_connectivity = true;
|
|
|
|
/* Copy connected nodes. */
|
|
return periodic_check(l);
|
|
});
|
|
/* If the NeedsConnectSolicitor failed, it might be due to
|
|
* Internet connection problems. */
|
|
bus.subscribe< Msg::TaskCompletion
|
|
>([this](Msg::TaskCompletion const& comp) {
|
|
if (!connector || checking_connectivity)
|
|
return Ev::lift();
|
|
if (comp.comment != "NeedsConnectSolicitor")
|
|
return Ev::lift();
|
|
if (!comp.failed)
|
|
return Ev::lift();
|
|
checking_connectivity = true;
|
|
return server_check();
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> server_check() {
|
|
/* No peers, so query connectivity via server connecting. */
|
|
return Ev::lift().then([this]() {
|
|
return check_server_connectivity()
|
|
.then([this](bool n_online) {
|
|
return set_online(n_online);
|
|
});
|
|
});
|
|
}
|
|
|
|
Ev::Io<bool> check_server_connectivity() {
|
|
assert(connector);
|
|
auto i = dist(Boss::random_engine);
|
|
return check_server(servers[i]).then([this](bool res) {
|
|
if (!res)
|
|
return check_all_servers();
|
|
else
|
|
return Ev::lift(true);
|
|
});
|
|
}
|
|
Ev::Io<bool> check_all_servers() {
|
|
using std::placeholders::_1;
|
|
return Ev::map( std::bind(&Impl::check_server, this, _1)
|
|
, servers
|
|
).then([this](std::vector<bool> results) {
|
|
/* If one of the servers passed, it turns out we
|
|
* are connected after all. */
|
|
for (auto const& r : results)
|
|
if (r)
|
|
return Ev::lift(true);
|
|
return Ev::lift(false);
|
|
});
|
|
}
|
|
Ev::Io<bool> check_server(std::pair<std::string, int> const& s) {
|
|
return threadpool.background<bool>([this, s]() {
|
|
auto fd = connector->connect(s.first, s.second);
|
|
return !!fd;
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> set_online(bool n_online) {
|
|
auto was_online = online;
|
|
online = n_online;
|
|
return Boss::log( bus, Info
|
|
, "InternetConnectionMonitor: %s."
|
|
, online ? "online" : "offline"
|
|
).then([this, was_online]() {
|
|
if (online) {
|
|
checking_connectivity = false;
|
|
/* If we moved from offline->online,
|
|
* raise NeedsConnect to get us back
|
|
* to gossiping.
|
|
*/
|
|
if (!was_online)
|
|
return bus.raise(Msg::NeedsConnect());
|
|
return Ev::lift();
|
|
}
|
|
return offline_quick_loop();
|
|
});
|
|
}
|
|
|
|
/* If we are offline, we should keep trying to connect in a
|
|
* quick 5-second loop.
|
|
* This is not an attack on the rest of the Internet, because
|
|
* we are offline and cannot access it anyway. */
|
|
Ev::Io<void> offline_quick_loop() {
|
|
return waiter.wait(5.0).then([this]() {
|
|
return check_server_connectivity();
|
|
}).then([this](bool result) {
|
|
if (result)
|
|
return set_online(true);
|
|
else
|
|
return offline_quick_loop();
|
|
});
|
|
}
|
|
|
|
Ev::Io<void> periodic_check(Msg::ListpeersAnalyzedResult const& r) {
|
|
/* No connected peers?
|
|
* Query servers directly.
|
|
*/
|
|
if ( r.connected_channeled.empty()
|
|
&& r.connected_unchanneled.empty()
|
|
)
|
|
return Boss::concurrent(server_check());
|
|
|
|
/* Otherwise pick a random peer to ping. */
|
|
auto peers = std::vector<Ln::NodeId>();
|
|
std::copy( r.connected_channeled.begin()
|
|
, r.connected_channeled.end()
|
|
, std::back_inserter(peers)
|
|
);
|
|
std::copy( r.connected_unchanneled.begin()
|
|
, r.connected_unchanneled.end()
|
|
, std::back_inserter(peers)
|
|
);
|
|
auto dist_peers = std::uniform_int_distribution<std::size_t>(
|
|
0, peers.size() - 1
|
|
);
|
|
auto i = dist_peers(Boss::random_engine);
|
|
return Boss::concurrent(ln_ping_check(peers[i]));
|
|
}
|
|
|
|
Ev::Io<void> ln_ping_check(Ln::NodeId const& p) {
|
|
/* Prefer to check our connectivity by pinging a
|
|
* peer directly.
|
|
* Use a timeout from Mod::Waiter; on some systems,
|
|
* even if you are disconnected physically, TCP
|
|
* connections are still kept alive for some time,
|
|
* leading to long delays in the LN-level `ping`.
|
|
*/
|
|
return Ev::lift().then([this, p]() {
|
|
return waiter.timed( ln_ping_timeout
|
|
, do_ln_ping(p)
|
|
).catching< Waiter::TimedOut
|
|
>([ this
|
|
](Waiter::TimedOut const& _) {
|
|
return Ev::lift(false);
|
|
});
|
|
}).then([this, p](bool online) {
|
|
if (online)
|
|
return Boss::log( bus, Debug
|
|
, "InternetConnectionMonitor: "
|
|
"ping %s success."
|
|
, std::string(p).c_str()
|
|
).then([this]() {
|
|
return set_online(true);
|
|
});
|
|
return Boss::log( bus, Debug
|
|
, "InternetConnectionMonitor: "
|
|
"ping %s failed."
|
|
, std::string(p).c_str()
|
|
).then([this]() {
|
|
/* Fall back to connecting to a server. */
|
|
return server_check();
|
|
});
|
|
});
|
|
}
|
|
|
|
Ev::Io<bool> do_ln_ping(Ln::NodeId const& p) {
|
|
return Ev::lift().then([this, p]() {
|
|
auto p_s = std::string(p);
|
|
auto presult = std::make_shared<bool>();
|
|
return rpc->command( "ping"
|
|
, Json::Out()
|
|
.start_object()
|
|
.field("id", p_s)
|
|
.end_object()
|
|
).then([this](Jsmn::Object _) {
|
|
return Ev::lift(true);
|
|
}).catching<RpcError>([](RpcError const& e) {
|
|
return Ev::lift(false);
|
|
}).then([this, presult](bool result) {
|
|
*presult = result;
|
|
return Boss::log( bus, Debug
|
|
, "InternetConnectionMonitor: "
|
|
"do_ln_ping: ping %s."
|
|
, result ? "success" : "failed"
|
|
);
|
|
}).then([presult]() {
|
|
return Ev::lift(*presult);
|
|
});
|
|
});
|
|
}
|
|
|
|
public:
|
|
Impl() =delete;
|
|
explicit
|
|
Impl( S::Bus& bus_
|
|
, Ev::ThreadPool& threadpool_
|
|
, Boss::Mod::Waiter& waiter_
|
|
) : bus(bus_)
|
|
, threadpool(threadpool_)
|
|
, waiter(waiter_)
|
|
, connector(nullptr)
|
|
, online(false)
|
|
, checking_connectivity(false)
|
|
{ start(); }
|
|
|
|
bool is_online() const { return online; }
|
|
};
|
|
|
|
InternetConnectionMonitor::InternetConnectionMonitor
|
|
(InternetConnectionMonitor&& o)
|
|
: pimpl(std::move(o.pimpl)) { }
|
|
InternetConnectionMonitor::~InternetConnectionMonitor() { }
|
|
InternetConnectionMonitor::InternetConnectionMonitor
|
|
( S::Bus& bus
|
|
, Ev::ThreadPool& threadpool
|
|
, Boss::Mod::Waiter& waiter
|
|
) : pimpl(Util::make_unique<Impl>(bus, threadpool, waiter))
|
|
{ }
|
|
|
|
bool InternetConnectionMonitor::is_online() const {
|
|
return pimpl->is_online();
|
|
}
|
|
|
|
}}
|