Kademlia based Mainline DHT (BEP5: https://www.bittorrent.org/beps/bep_0005.html) implementation in Erlang.
Derivative project from https://github.com/bartima3us/erl-bittorrent
- Buckets can contain K nodes where by default K=8.
- If bucket is full, all other nodes will be placed in the pseudo-bucket - not assigned nodes list.
- Every node in the bucket is pinged every 6-12 min (random amount from the range).
- Every bucket is checked every 1-3 min (random amount from the range).
- If during the check some
active
node last respond was more than 14 min ago - that node becomessuspicious
and it is tried to ping one more time. - If during the check some
suspicious
node last respond was more than 15 min ago - that node becomesnot active
. - If node in the bucket becomes
not active
, it can be automatically replaced withactive
node from not assigned nodes list.
Start ErLine DHT node explicitly. Should be used only when {auto_start, false}
in sys.config. If node port is specified, node will try to start on that port. Otherwise port will be taken from sys.config or will be selected randomly.
erline_dht:start_node(
NodeName :: atom()
) -> ok
and
erline_dht:start_node(
NodeName :: atom(),
Port :: inet:port_number()
) -> ok
Stop ErLine DHT node:
erline_dht:stop_node(
NodeName :: atom()
) -> ok
Add a new node with unknown hash to the bucket:
erline_dht:add_node_to_bucket(
NodeName :: atom(),
Ip :: inet:ip_address(),
Port :: inet:port_number()
) -> ok
Add a new node with known hash to the bucket (if bucket is full, node will be added to the not assigned nodes list):
erline_dht:add_node(
NodeName :: atom(),
Ip :: inet:ip_address(),
Port :: inet:port_number(),
Hash :: binary()
) -> ok.
Try to find peers for the info hash in the network:
erline_dht:get_peers(
NodeName :: atom(),
InfoHash :: binary()
) -> ok.
Try to get peers for the info hash from one node:
erline_dht:get_peers(
NodeName :: atom(),
Ip :: inet:ip_address(),
Port :: inet:port_number(),
InfoHash :: binary()
) -> ok.
Return all nodes information in the bucket (distance()
will be: 0..erlang:bit_size(YourNodeHash)
or by default: 0..160
):
erline_dht:get_all_nodes_in_bucket(
NodeName :: atom(),
Distance :: distance()
) -> [#{ip => inet:ip_address(),
port => inet:port_number(),
hash => binary(),
status => active | suspicious | not_active,
last_changed => calendar:datetime()}].
Return all not assigned nodes information:
erline_dht:get_not_assigned_nodes(
NodeName :: atom()
) ->
[#{ip => inet:ip_address(),
port => inet:port_number(),
hash => binary(),
last_changed => calendar:datetime()}].
Return not assigned nodes of the specified distance information:
erline_dht:get_not_assigned_nodes(
NodeName :: atom(),
Distance :: distance()
) -> [#{ip => inet:ip_address(),
port => inet:port_number(),
hash => binary(),
last_changed => calendar:datetime()}].
Return amount of nodes in every bucket:
erline_dht:get_buckets_filling(
NodeName :: atom()
) -> [#{distance => distance(), nodes => non_neg_integer()}].
Return UDP socket port of the ErLine DHT client:
erline_dht:get_port(
NodeName :: atom()
) -> Port :: inet:port_number().
Return node hash of the ErLine DHT client:
erline_dht:get_hash(
NodeName :: atom()
) -> Hash :: binary().
Return all known info hashes and their peers IP and port:
erline_dht:get_info_hashes(
NodeName :: atom()
) -> [#{info_hash => binary(), peers => [{inet:ip_address(), inet:port_number()}]}].
Return event manager pid:
erline_dht:get_event_mgr_pid(
NodeName :: atom()
) -> EventMgrPid :: pid()
Set peer port. If port is set, it will be used as your port in announce_peer
request. Otherwise ErLine DHT node port will be used as your peer port. Default: atom undefined
(not set):
erline_dht:set_peer_port(
NodeName :: atom(),
Port :: inet:port_number()
) -> ok
Default sys.config for all started nodes:
{erline_dht, [
{auto_start, true},
{auto_bootstrap_nodes, [
{"router.bittorrent.com", 6881},
{"dht.transmissionbt.com", 6881},
{"router.utorrent.com", 6881},
{"router.bitcomet.com", 6881},
{"dht.aelitis.com", 6881}
]},
{db_mod, erline_dht_db_ets},
{limit_nodes, true},
{k, 8},
{port, 0},
{node_hash, 20}
]}
auto_start
- Whether start node on application start or not. Iftrue
- node is started automatically and node name is atomnode1
. Iffalse
- node must be started explicitly witherline_dht:start_node/1
orerline_dht:start_node/2
function.auto_bootstrap_nodes
- List of initial nodes and their port used in bootstrapping process just after ErLineDHT start.db_mod
- Module used for database queries encapsulation. ErLineDHT uses ETS by default but it can be easily changed by another engine.limit_nodes
- Iftrue
- ErLine DHT will clear some old nodes from time to time. Iffalse
- ErLine DHT will keep all known nodes. Warning! Since there are approximately 10-25 million of Mainline DHT network users, keeping all nodes may consume a lot of memory.k
- Amount of nodes in a single bucket.port
- If0
- ErLineDHT will open any free port. Ifinet:port_number()
- first of all, ErLineDHT will try to use specified port but in case it's already in use, ErLineDHT will open socket on any free port.node_hash
- Ifpos_integer()
- ErLineDHT will generate random node hash by specified amount. Ifbinary()
- ErLineDHT will use specified node hash.
If custom config for every node is required, use such notation:
{erline_dht, [
{node_name1, [
{auto_start, true},
{auto_bootstrap_nodes, [
{"router.bittorrent.com", 6881},
{"dht.transmissionbt.com", 6881},
{"router.utorrent.com", 6881},
{"router.bitcomet.com", 6881},
{"dht.aelitis.com", 6881}
]},
{db_mod, erline_dht_db_ets},
{limit_nodes, true},
{k, 8},
{port, 0},
{node_hash, 20}
]},
{node_name2, [
{auto_start, true},
{auto_bootstrap_nodes, []},
{db_mod, erline_dht_db_ets},
{limit_nodes, false},
{k, 8},
{port, 6881},
{node_hash, 20}
]}
]}
All received queries and responses can be subscribed.
Get event manager pid and attach event handler
EventMgrPid = erline_dht:get_event_mgr_pid(NodeName).
gen_event:add_handler(EventMgrPid, your_dht_event_handler, []).
Or you can always use event manager name, which is registered as atom: 'erline_dht_[YOUR_NODE_NAME]$event_manager'
. For example:
gen_event:add_handler('erline_dht_node1$event_manager', your_dht_event_handler, []).
Events which should be handled in the attached handler:
Received ping query from the node:
{ping, q, Ip :: inet:ip_address(), Port :: inet:port_number(), NodeHash :: binary()}
Received ping response from the node:
{ping, r, Ip :: inet:ip_address(), Port :: inet:port_number(), NodeHash :: binary()}
Received find_node query from the node:
{find_node, q, Ip :: inet:ip_address(), Port :: inet:port_number(), {NodeHash :: binary(), Target :: binary()}}
Received find_node response from the node:
{find_node, r, Ip :: inet:ip_address(), Port :: inet:port_number(), {NodeHash :: binary(), Nodes :: [#{ip => inet:ip_address(), port => inet:port_number(), hash => binary()}]}}
Received get_peers query from the node:
{get_peers, q, Ip :: inet:ip_address(), Port :: inet:port_number(), {NodeHash :: binary(), InfoHash :: binary()}
Received get_peers response with nodes list from the node:
{get_peers, r, Ip :: inet:ip_address(), Port :: inet:port_number(), {nodes, NodeHash :: binary(), InfoHash :: binary(), Nodes :: [#{ip => inet:ip_address(), port => inet:port_number(), hash => binary()}]}}
Received get_peers response with peers list from the node:
{get_peers, r, Ip :: inet:ip_address(), Port :: inet:port_number(), {peers, NodeHash :: binary(), InfoHash :: binary(), Peers :: [#{ip => inet:ip_address(), port => inet:port_number()}]}}
Received announce_peer query from the node:
{announce_peer, q, Ip :: inet:ip_address(), Port :: inet:port_number(), {NodeHash :: binary(), InfoHash :: binary(), PeerPort :: inet:port_number(), Token :: binary()}}
Received announce_peer response from the node:
{announce_peer, r, Ip :: inet:ip_address(), Port :: inet:port_number(), NodeHash :: binary()}
Received error response from the node:
{error, r, Ip :: inet:ip_address(), Port :: inet:port_number(), {ErrorCode :: 201 | 202 | 203 | 204, ErrorReason :: binary()}
EUnit and CT tests
$ make tests