Skip to content

Commit cd26cad

Browse files
authored
Merge pull request #1555 from pmconrad/network_mapper
Add network mapper from bitshares-1
2 parents c539336 + 21a4d76 commit cd26cad

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed

programs/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ add_subdirectory( witness_node )
55
add_subdirectory( delayed_node )
66
add_subdirectory( js_operation_serializer )
77
add_subdirectory( size_checker )
8+
add_subdirectory( network_mapper )
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
add_executable( network_mapper network_mapper.cpp )
2+
target_link_libraries( network_mapper fc graphene_chain graphene_net )
3+
+317
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
#include <fc/crypto/elliptic.hpp>
2+
#include <fc/io/json.hpp>
3+
#include <fc/exception/exception.hpp>
4+
#include <fc/io/raw_variant.hpp>
5+
#include <fc/network/ip.hpp>
6+
#include <fc/network/resolve.hpp>
7+
#include <fc/thread/future.hpp>
8+
#include <fstream>
9+
#include <iostream>
10+
#include <queue>
11+
#include <future>
12+
13+
#include <graphene/chain/database.hpp>
14+
#include <graphene/net/peer_connection.hpp>
15+
16+
class peer_probe : public graphene::net::peer_connection_delegate
17+
{
18+
public:
19+
bool _peer_closed_connection;
20+
bool _we_closed_connection;
21+
graphene::net::peer_connection_ptr _connection;
22+
std::vector<graphene::net::address_info> _peers;
23+
fc::ecc::public_key _node_id;
24+
fc::ip::endpoint _remote;
25+
bool _connection_was_rejected;
26+
bool _done;
27+
fc::promise<void>::ptr _probe_complete_promise;
28+
29+
public:
30+
peer_probe() :
31+
_peer_closed_connection(false),
32+
_we_closed_connection(false),
33+
_connection(graphene::net::peer_connection::make_shared(this)),
34+
_connection_was_rejected(false),
35+
_done(false),
36+
_probe_complete_promise(fc::promise<void>::ptr(new fc::promise<void>("probe_complete")))
37+
{}
38+
39+
void start(const fc::ip::endpoint& endpoint_to_probe,
40+
const fc::ecc::private_key& my_node_id,
41+
const graphene::chain::chain_id_type& chain_id)
42+
{
43+
_remote = endpoint_to_probe;
44+
fc::future<void> connect_task = fc::async([this](){ _connection->connect_to(_remote); }, "connect_task");
45+
try
46+
{
47+
connect_task.wait(fc::seconds(10));
48+
}
49+
catch (const fc::timeout_exception&)
50+
{
51+
ilog("timeout connecting to node ${endpoint}", ("endpoint", endpoint_to_probe));
52+
connect_task.cancel(__FUNCTION__);
53+
throw;
54+
}
55+
56+
fc::sha256::encoder shared_secret_encoder;
57+
fc::sha512 shared_secret = _connection->get_shared_secret();
58+
shared_secret_encoder.write(shared_secret.data(), sizeof(shared_secret));
59+
fc::ecc::compact_signature signature = my_node_id.sign_compact(shared_secret_encoder.result());
60+
61+
graphene::net::hello_message hello("network_mapper",
62+
GRAPHENE_NET_PROTOCOL_VERSION,
63+
fc::ip::address(), 0, 0,
64+
my_node_id.get_public_key(),
65+
signature,
66+
chain_id,
67+
fc::variant_object());
68+
69+
_connection->send_message(hello);
70+
}
71+
72+
void on_message(graphene::net::peer_connection* originating_peer,
73+
const graphene::net::message& received_message) override
74+
{
75+
graphene::net::message_hash_type message_hash = received_message.id();
76+
dlog( "handling message ${type} ${hash} size ${size} from peer ${endpoint}",
77+
( "type", graphene::net::core_message_type_enum(received_message.msg_type ) )("hash", message_hash )("size", received_message.size )("endpoint", originating_peer->get_remote_endpoint() ) );
78+
switch ( received_message.msg_type )
79+
{
80+
case graphene::net::core_message_type_enum::hello_message_type:
81+
on_hello_message( originating_peer, received_message.as<graphene::net::hello_message>() );
82+
break;
83+
case graphene::net::core_message_type_enum::connection_accepted_message_type:
84+
on_connection_accepted_message( originating_peer, received_message.as<graphene::net::connection_accepted_message>() );
85+
break;
86+
case graphene::net::core_message_type_enum::connection_rejected_message_type:
87+
on_connection_rejected_message( originating_peer, received_message.as<graphene::net::connection_rejected_message>() );
88+
break;
89+
case graphene::net::core_message_type_enum::address_request_message_type:
90+
on_address_request_message( originating_peer, received_message.as<graphene::net::address_request_message>() );
91+
break;
92+
case graphene::net::core_message_type_enum::address_message_type:
93+
on_address_message( originating_peer, received_message.as<graphene::net::address_message>() );
94+
break;
95+
case graphene::net::core_message_type_enum::closing_connection_message_type:
96+
on_closing_connection_message( originating_peer, received_message.as<graphene::net::closing_connection_message>() );
97+
break;
98+
default:
99+
break;
100+
}
101+
}
102+
103+
void on_hello_message(graphene::net::peer_connection* originating_peer,
104+
const graphene::net::hello_message& hello_message_received)
105+
{
106+
_node_id = hello_message_received.node_public_key;
107+
if (hello_message_received.user_data.contains("node_id"))
108+
originating_peer->node_id = hello_message_received.user_data["node_id"].as<graphene::net::node_id_t>( 1 );
109+
originating_peer->send_message(graphene::net::connection_rejected_message());
110+
}
111+
112+
void on_connection_accepted_message(graphene::net::peer_connection* originating_peer,
113+
const graphene::net::connection_accepted_message& connection_accepted_message_received)
114+
{
115+
_connection_was_rejected = false;
116+
originating_peer->send_message(graphene::net::address_request_message());
117+
}
118+
119+
void on_connection_rejected_message( graphene::net::peer_connection* originating_peer,
120+
const graphene::net::connection_rejected_message& connection_rejected_message_received )
121+
{
122+
_connection_was_rejected = true;
123+
originating_peer->send_message(graphene::net::address_request_message());
124+
}
125+
126+
void on_address_request_message(graphene::net::peer_connection* originating_peer,
127+
const graphene::net::address_request_message& address_request_message_received)
128+
{
129+
originating_peer->send_message(graphene::net::address_message());
130+
}
131+
132+
133+
void on_address_message(graphene::net::peer_connection* originating_peer,
134+
const graphene::net::address_message& address_message_received)
135+
{
136+
_peers = address_message_received.addresses;
137+
originating_peer->send_message(graphene::net::closing_connection_message("Thanks for the info"));
138+
_we_closed_connection = true;
139+
}
140+
141+
void on_closing_connection_message(graphene::net::peer_connection* originating_peer,
142+
const graphene::net::closing_connection_message& closing_connection_message_received)
143+
{
144+
if (_we_closed_connection)
145+
_connection->close_connection();
146+
else
147+
_peer_closed_connection = true;
148+
}
149+
150+
void on_connection_closed(graphene::net::peer_connection* originating_peer) override
151+
{
152+
_done = true;
153+
_probe_complete_promise->set_value();
154+
}
155+
156+
graphene::net::message get_message_for_item(const graphene::net::item_id& item) override
157+
{
158+
return graphene::net::item_not_available_message(item);
159+
}
160+
161+
void wait( const fc::microseconds& timeout_us )
162+
{
163+
_probe_complete_promise->wait( timeout_us );
164+
}
165+
};
166+
167+
int main(int argc, char** argv)
168+
{
169+
std::queue<fc::ip::endpoint> nodes_to_visit;
170+
std::set<fc::ip::endpoint> nodes_to_visit_set;
171+
std::set<fc::ip::endpoint> nodes_already_visited;
172+
173+
if ( argc < 3 ) {
174+
std::cerr << "Usage: " << argv[0] << " <chain-id> <seed-addr> [<seed-addr> ...]\n";
175+
exit(1);
176+
}
177+
178+
const graphene::chain::chain_id_type chain_id( argv[1] );
179+
for ( int i = 2; i < argc; i++ )
180+
{
181+
std::string ep(argv[i]);
182+
uint16_t port;
183+
auto pos = ep.find(':');
184+
if (pos > 0)
185+
port = boost::lexical_cast<uint16_t>( ep.substr( pos+1, ep.size() ) );
186+
else
187+
port = 1776;
188+
for (const auto& addr : fc::resolve( ep.substr( 0, pos > 0 ? pos : ep.size() ), port ))
189+
nodes_to_visit.push( addr );
190+
}
191+
192+
fc::path data_dir = fc::temp_directory_path() / ("network_map_" + (fc::string) chain_id);
193+
fc::create_directories(data_dir);
194+
195+
fc::ip::endpoint seed_node1 = nodes_to_visit.front();
196+
197+
fc::ecc::private_key my_node_id = fc::ecc::private_key::generate();
198+
std::map<graphene::net::node_id_t, graphene::net::address_info> address_info_by_node_id;
199+
std::map<graphene::net::node_id_t, std::vector<graphene::net::address_info> > connections_by_node_id;
200+
std::vector<std::shared_ptr<peer_probe>> probes;
201+
202+
while (!nodes_to_visit.empty() || !probes.empty())
203+
{
204+
while (!nodes_to_visit.empty())
205+
{
206+
fc::ip::endpoint remote = nodes_to_visit.front();
207+
nodes_to_visit.pop();
208+
nodes_to_visit_set.erase( remote );
209+
nodes_already_visited.insert( remote );
210+
211+
try
212+
{
213+
std::shared_ptr<peer_probe> probe(new peer_probe());
214+
probe->start(remote, my_node_id, chain_id);
215+
probes.emplace_back( std::move( probe ) );
216+
}
217+
catch (const fc::exception&)
218+
{
219+
std::cerr << "Failed to connect " << fc::string(remote) << " - skipping!" << std::endl;
220+
}
221+
}
222+
223+
if (!probes.empty())
224+
{
225+
fc::yield();
226+
std::vector<std::shared_ptr<peer_probe>> running;
227+
for ( auto& probe : probes ) {
228+
if (probe->_probe_complete_promise->error())
229+
{
230+
std::cerr << fc::string(probe->_remote) << " ran into an error!\n";
231+
continue;
232+
}
233+
if (!probe->_probe_complete_promise->ready())
234+
{
235+
running.push_back( probe );
236+
continue;
237+
}
238+
239+
if( probe->_node_id.valid() )
240+
{
241+
graphene::net::address_info this_node_info;
242+
this_node_info.direction = graphene::net::peer_connection_direction::outbound;
243+
this_node_info.firewalled = graphene::net::firewalled_state::not_firewalled;
244+
this_node_info.remote_endpoint = probe->_remote;
245+
this_node_info.node_id = probe->_node_id;
246+
247+
connections_by_node_id[this_node_info.node_id] = probe->_peers;
248+
if (address_info_by_node_id.find(this_node_info.node_id) == address_info_by_node_id.end())
249+
address_info_by_node_id[this_node_info.node_id] = this_node_info;
250+
}
251+
252+
for (const graphene::net::address_info& info : probe->_peers)
253+
{
254+
if (nodes_already_visited.find(info.remote_endpoint) == nodes_already_visited.end() &&
255+
info.firewalled == graphene::net::firewalled_state::not_firewalled &&
256+
nodes_to_visit_set.find(info.remote_endpoint) == nodes_to_visit_set.end())
257+
{
258+
nodes_to_visit.push(info.remote_endpoint);
259+
nodes_to_visit_set.insert(info.remote_endpoint);
260+
}
261+
if (address_info_by_node_id.find(info.node_id) == address_info_by_node_id.end())
262+
address_info_by_node_id[info.node_id] = info;
263+
}
264+
}
265+
probes = std::move( running );
266+
std::cout << address_info_by_node_id.size() << " checked, "
267+
<< probes.size() << " active, "
268+
<< nodes_to_visit.size() << " to do\n";
269+
}
270+
}
271+
272+
graphene::net::node_id_t seed_node_id;
273+
std::set<graphene::net::node_id_t> non_firewalled_nodes_set;
274+
for (const auto& address_info_for_node : address_info_by_node_id)
275+
{
276+
if (address_info_for_node.second.remote_endpoint == seed_node1)
277+
seed_node_id = address_info_for_node.first;
278+
if (address_info_for_node.second.firewalled == graphene::net::firewalled_state::not_firewalled)
279+
non_firewalled_nodes_set.insert(address_info_for_node.first);
280+
}
281+
std::set<graphene::net::node_id_t> seed_node_connections;
282+
for (const graphene::net::address_info& info : connections_by_node_id[seed_node_id])
283+
seed_node_connections.insert(info.node_id);
284+
std::set<graphene::net::node_id_t> seed_node_missing_connections;
285+
std::set_difference(non_firewalled_nodes_set.begin(), non_firewalled_nodes_set.end(),
286+
seed_node_connections.begin(), seed_node_connections.end(),
287+
std::inserter(seed_node_missing_connections, seed_node_missing_connections.end()));
288+
seed_node_missing_connections.erase(seed_node_id);
289+
290+
std::ofstream dot_stream((data_dir / "network_graph.dot").string().c_str());
291+
292+
dot_stream << "graph G {\n";
293+
dot_stream << " // Total " << address_info_by_node_id.size() << " nodes, firewalled: " << (address_info_by_node_id.size() - non_firewalled_nodes_set.size())
294+
<< ", non-firewalled: " << non_firewalled_nodes_set.size() << "\n";
295+
dot_stream << " // Seed node is " << (std::string)address_info_by_node_id[seed_node_id].remote_endpoint << " id: " << fc::variant( seed_node_id, 1 ).as_string() << "\n";
296+
dot_stream << " // Seed node is connected to " << connections_by_node_id[seed_node_id].size() << " nodes\n";
297+
dot_stream << " // Seed node is missing connections to " << seed_node_missing_connections.size() << " non-firewalled nodes:\n";
298+
for (const graphene::net::node_id_t& id : seed_node_missing_connections)
299+
dot_stream << " // " << (std::string)address_info_by_node_id[id].remote_endpoint << "\n";
300+
301+
dot_stream << " layout=\"circo\";\n";
302+
303+
for (const auto& address_info_for_node : address_info_by_node_id)
304+
{
305+
dot_stream << " \"" << fc::variant( address_info_for_node.first, 1 ).as_string() << "\"[label=\"" << (std::string)address_info_for_node.second.remote_endpoint << "\"";
306+
if (address_info_for_node.second.firewalled != graphene::net::firewalled_state::not_firewalled)
307+
dot_stream << ",shape=rectangle";
308+
dot_stream << "];\n";
309+
}
310+
for (auto& node_and_connections : connections_by_node_id)
311+
for (const graphene::net::address_info& this_connection : node_and_connections.second)
312+
dot_stream << " \"" << fc::variant( node_and_connections.first, 2 ).as_string() << "\" -- \"" << fc::variant( this_connection.node_id, 1 ).as_string() << "\";\n";
313+
314+
dot_stream << "}\n";
315+
316+
return 0;
317+
}

0 commit comments

Comments
 (0)