Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review full codebase #1

Closed
wants to merge 121 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
52be1c1
wip: add test vector for routing_table.closest()
Nuhvi Oct 19, 2023
b47aa46
feat: routing_table.closest() tested against javascript implementations
Nuhvi Oct 19, 2023
28da349
feat: routing_table.closest() complex test
Nuhvi Oct 19, 2023
b224aa7
refactor: add kademlia directory mirroring libtorrent
Nuhvi Oct 21, 2023
fb7553a
docs: add disclaimer in the readme
Nuhvi Oct 21, 2023
6ece950
feat: bencode encode and decode ping request/response
Nuhvi Oct 21, 2023
411dab5
feat: add next_tid to RPC
Nuhvi Oct 23, 2023
bbd666d
feat: rpc listener loop, thread, shutdown
Nuhvi Oct 24, 2023
8366661
feat: rpc ping block until response
Nuhvi Oct 24, 2023
459f9fd
refactor: use rx.try_recv() instead of recv_timeout()
Nuhvi Oct 24, 2023
dc29323
lint: follow clippy suggestions
Nuhvi Oct 24, 2023
a9753e3
feat: add find_node rpc message
Nuhvi Oct 24, 2023
cd6b8d4
docs: add todo list in README.md
Nuhvi Oct 24, 2023
40d047b
test: add unit tests for messages::public find_node messages
Nuhvi Oct 25, 2023
8c4963b
refactor: change Message.transaction_id to u16
Nuhvi Oct 26, 2023
578fb1c
refactor: a good enough solution for accepting incoming requests and …
Nuhvi Oct 27, 2023
d7f4d85
feat(rpc): add request_timeout
Nuhvi Oct 27, 2023
6105d13
chore: cleanup
Nuhvi Oct 27, 2023
d8b3bf3
feat(rpc): add Version and read_only support
Nuhvi Oct 27, 2023
6283d99
feat(rpc): more support for BEP0043
Nuhvi Oct 27, 2023
f293283
refactor: add rpc.run() helper method and remove Arc for UdpSocket
Nuhvi Oct 27, 2023
e4852d4
feat: basic Dht struct and testing bootstraping
Nuhvi Oct 27, 2023
fad7b74
fix: messages::public::transaction_id()
Nuhvi Oct 28, 2023
f33ef55
refactor: move Id unit tests to common::id
Nuhvi Oct 28, 2023
f57a45d
fix: rpc.try_recv_from() not blocking
Nuhvi Oct 28, 2023
1aec671
fix: set udp socket to nonblocking
Nuhvi Oct 28, 2023
9193add
refactor: move KrpcSocket out of Rpc
Nuhvi Oct 28, 2023
62e99ec
refactor: move messages/public.rs to messages/mod.rs
Nuhvi Oct 29, 2023
31c390d
use recv_from instead of tick, and add some docs
Nuhvi Oct 29, 2023
8cd3587
feat: add kbucket.rs
Nuhvi Oct 30, 2023
0cb10f6
fix: socket.rseponse() and socket.error() expect transaction_id of th…
Nuhvi Oct 31, 2023
0a724b4
feat: Id from hex &str
Nuhvi Oct 31, 2023
8f9edb0
wip: actor in dht.rs
Nuhvi Nov 4, 2023
52c267e
wip: update and cleanup socket.rs
Nuhvi Nov 5, 2023
e8978e6
feat: stable minimal well tested socket.rs
Nuhvi Nov 5, 2023
009d0f2
wip: cleanup and simplifying routing_table
Nuhvi Nov 6, 2023
d82cd50
feat: socket.rs ingores request if it's in a read-only mode
Nuhvi Nov 6, 2023
9488a88
feat: respond to find_node()
Nuhvi Nov 6, 2023
ee008be
feat: rpc.populate() working and tested
Nuhvi Nov 6, 2023
93bb5bf
chore: clippy
Nuhvi Nov 6, 2023
5a396d6
feat: bootstrapping as an iterative query
Nuhvi Nov 8, 2023
81bfaee
fix: don't add same node twice in a bucket
Nuhvi Nov 8, 2023
0c2fc56
feat: handle adding node with same id, different address, and evictin…
Nuhvi Nov 8, 2023
ed88364
feat: rpc intercepts all requests to add_node and add_closer_nodes
Nuhvi Nov 9, 2023
20517ef
fix: serious fixing of how bootstrapping works
Nuhvi Nov 10, 2023
c4e1e15
feat: skip adding read-only nodes to the routing_table, finishing bep43
Nuhvi Nov 10, 2023
e4333a1
feat: add get_peers message
Nuhvi Nov 10, 2023
bac5acd
feat: get_peers query working and resolving from other implementations
Nuhvi Nov 10, 2023
9c29632
lint: clippy
Nuhvi Nov 11, 2023
0eb38da
lint: clippy
Nuhvi Nov 11, 2023
de94ae6
refactor: query and rpc
Nuhvi Nov 11, 2023
e506263
feat: dht::get_peers(info_hash)
Nuhvi Nov 11, 2023
fbb62dd
feat: add example get_peers.r
Nuhvi Nov 12, 2023
0629e79
feat: support multiple queries
Nuhvi Nov 13, 2023
dacf577
feat: add tokens generator and secret manager
Nuhvi Nov 14, 2023
e2a805f
feat: add peers store for info_hashes
Nuhvi Nov 14, 2023
291d7bb
feat: add tokens and versions to response.from
Nuhvi Nov 14, 2023
2b4ce37
wip: add response module
Nuhvi Nov 14, 2023
45619e6
feat: announce_peer actually works client mode
Nuhvi Nov 14, 2023
0569495
chore: clippy
Nuhvi Nov 14, 2023
232e535
refactor: rklaehn feedback
Nuhvi Nov 14, 2023
de91f6c
Merge branch 'dev'
Nuhvi Nov 14, 2023
9181753
refactor: response StoreItem struct for accessing closest nodes and s…
Nuhvi Nov 15, 2023
41e8df1
rename StoreResponse to StoreQueryMetadata
Nuhvi Nov 15, 2023
4fbb08a
feat: add Id from [u8; 20]
Nuhvi Nov 15, 2023
1ead340
feat: testnet and builder
Nuhvi Nov 15, 2023
1688ed3
feat: add Builder::port
Nuhvi Nov 15, 2023
5c843eb
fix: Node equality
Nuhvi Nov 15, 2023
c84ce55
docs: add comments and examples for clone and get_peers
Nuhvi Nov 16, 2023
4a3b6b1
chore: clippy
Nuhvi Nov 17, 2023
504e153
feat: add KrpcSocket::bind()
Nuhvi Nov 17, 2023
aa30ed5
feat: dht returns routing_table and set port correctly or panics
Nuhvi Nov 17, 2023
ccb66a2
wip: small stuff
Nuhvi Nov 17, 2023
e63fe2e
feat: add DHT Get arbitrary value message
Nuhvi Nov 18, 2023
09369d3
fix(packets): GetValue response message priority and tests
Nuhvi Nov 19, 2023
e152e71
perf: reducing the tick interval increases the resolution speed by 4x
Nuhvi Nov 19, 2023
55d6563
chore(example): add unit notation
Nuhvi Nov 19, 2023
f7742c2
chore: remove unnecessary return type in dht.run()
Nuhvi Nov 19, 2023
5c91307
chore(publish): 0.2.0
Nuhvi Nov 19, 2023
9d1be09
updated gitignore
Nuhvi Nov 19, 2023
3a24007
feat: add partial support for getting Immutable data without verifyin…
Nuhvi Nov 19, 2023
d3f0dfd
chore: clippy
Nuhvi Nov 19, 2023
4fd0ce7
feat: validate incoming immutable values
Nuhvi Nov 19, 2023
f161918
switch to flume channels
rklaehn Nov 20, 2023
63648d4
Async support
rklaehn Nov 20, 2023
f5a6bdd
Disable async in flume unless async feature is enabled.
rklaehn Nov 20, 2023
0fc13a3
Add a very basic test of the async api
rklaehn Nov 20, 2023
7f5adc6
Merge pull request #2 from Nuhvi/async
Nuhvi Nov 20, 2023
3f19cfb
chore: clippy
Nuhvi Nov 20, 2023
98d0699
Add basic CI
rklaehn Nov 20, 2023
8df1eaf
fix clippy
rklaehn Nov 20, 2023
4cb75c2
Merge pull request #3 from Nuhvi/ci
Nuhvi Nov 20, 2023
d8e958b
chore: documenting purpose of each module
Nuhvi Nov 20, 2023
319aea2
fix: use socket read_timeout instead of non-blocking
Nuhvi Nov 20, 2023
ee25c7a
feat: put and get immutable data as read_only node
Nuhvi Nov 20, 2023
8e0ebe1
feat: add async versions of get and put immutable
Nuhvi Nov 20, 2023
4cb9b56
feat: verify mutable item responses signatures
Nuhvi Nov 21, 2023
e1d8875
Put all the async stuff in a single file that is feature gated
rklaehn Nov 21, 2023
d6e451b
Make response constructor pub(crate)
rklaehn Nov 21, 2023
9a558d0
Merge pull request #4 from Nuhvi/async2
Nuhvi Nov 21, 2023
85b8195
feat: put mutable
Nuhvi Nov 21, 2023
7673678
feat: add mutable endpoints to async_dht
Nuhvi Nov 21, 2023
e488b8d
fix: use unbounded channels for ResponseMessage<_>
Nuhvi Nov 21, 2023
125ab46
chore: fix lint issue
Nuhvi Nov 21, 2023
39bea3f
chore(publish): 0.3.0
Nuhvi Nov 21, 2023
4803df9
fix: test issue
Nuhvi Nov 21, 2023
148905e
feat: update MutableItem to work with Pkarr
Nuhvi Nov 22, 2023
8b1fa1d
feat: use bytes::Bytes
Nuhvi Nov 22, 2023
373eb60
feat: internal routing table maintenance
Nuhvi Nov 22, 2023
53dbcc2
chore(publish): 0.4.0
Nuhvi Nov 22, 2023
f54a83b
fix: allow cloning response
Nuhvi Nov 23, 2023
3ed5972
chore: publish 0.4.1
Nuhvi Nov 23, 2023
a0ee82c
fix: don't prune done queries too soon
Nuhvi Nov 23, 2023
23a7e5f
chore: publish 0.4.2
Nuhvi Nov 23, 2023
3fb423b
chore: remove bad comment
Nuhvi Nov 23, 2023
5d2a5a9
feat: change get_mutable() api to not require VerifyingKey
Nuhvi Nov 24, 2023
b44d6f0
chore: publish 0.5.0
Nuhvi Nov 24, 2023
d24f6ad
feat: add bep_0042 support
Nuhvi Nov 25, 2023
4f59cbc
update README.md
Nuhvi Nov 25, 2023
994728d
Merge pull request #5 from Nuhvi/bep_0042
Nuhvi Nov 25, 2023
0e8b64b
publish 0.6.0
Nuhvi Nov 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Rust CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true

- name: Check formatting
run: cargo fmt -- --check

- name: Lint with Clippy
run: cargo clippy -- -D warnings

- name: Build
run: cargo build --verbose

- name: Run tests
run: cargo test --verbose
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
Cargo.lock
reference
22 changes: 20 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mainline"
version = "0.1.0"
version = "0.6.0"
authors = ["nuh.dev"]
edition = "2018"
description = "Simple, robust, BitTorrent's Mainline DHT implementation"
Expand All @@ -10,4 +10,22 @@ keywords = ["bittorrent", "torrent", "dht", "kademlia", "mainline"]
categories = ["network-programming"]
repository = "https://github.com/nuhvi/mainline"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Nuhvi marked this conversation as resolved.
Show resolved Hide resolved
rand = "0.8.5"
serde_bencode = "^0.2.4"
serde = { version = "1.0.133", features = ["derive"] }
serde_bytes = "0.11.5"
thiserror = "1.0.49"
crc = "3.0.1"
sha1_smol = "1.0.0"
flume = { version = "0.11.0", features = ["select", "eventual-fairness"], default-features = false }
ed25519-dalek = "2.1.0"
bytes = "1.5.0"

[dev-dependencies]
clap = { version = "4.4.8", features = ["derive"] }
futures = "0.3.29"

[features]
async = ["flume/async"]
default = ["async"]
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,50 @@
# Mainline

WIP mainline rust implementation.
Simple, robust, BitTorrent's [Mainline](https://en.wikipedia.org/wiki/Mainline_DHT) DHT implementation.

This library is focused on being the best and simplest Rust client for Mainline, especially focused on reliable and fast time-to-first-response.

It should work as a routing / storing node as well, but if you want to run a reliable node to support the network, you might be better off running [libtorrent](https://libtorrent.org/) for now.

## Get started

Check the [Examples](./examples).

## Features

### Client

Running as a client, means you can store and query for values on the DHT, but not accept any incoming requests.

```rust
use mainline::Dht;

let dht = Dht::client(); // or Dht::default();
```

Supported BEPs:
- [x] [BEP0005 DHT Protocol](https://www.bittorrent.org/beps/bep_0005.html)
- [X] [BEP0042 DHT Security extension](https://www.bittorrent.org/beps/bep_0042.html)
- [x] [BEP0043 Read-only DHT Nodes](https://www.bittorrent.org/beps/bep_0043.html)
- [x] [BEP0044 Storing arbitrary data in the DHT](https://www.bittorrent.org/beps/bep_0044.html)

### Server

Running as a server is the same as a client, but you also respond to incoming requests and serve as a routing and storing node, supporting the general routing of the DHT, and contributing to the storage capacity of the DHT.

```rust
use mainline::Dht;

let dht = Dht::server(); // or Dht::builder::as_server().build();
```

Supported BEPs:
- [x] [BEP0005 DHT Protocol](https://www.bittorrent.org/beps/bep_0005.html)
- [ ] [BEP0042 DHT Security extension](https://www.bittorrent.org/beps/bep_0042.html)
- [x] [BEP0043 Read-only DHT Nodes](https://www.bittorrent.org/beps/bep_0043.html)
- [ ] [BEP0044 Storing arbitrary data in the DHT](https://www.bittorrent.org/beps/bep_0044.html)


## Acknowledgment

This implementation was possible thanks to [Webtorrent's Bittorrent-dht](https://github.com/webtorrent/bittorrent-dht) as a reference, and [Rustydht-lib](https://github.com/raptorswing/rustydht-lib) that saved me a lot of time, especially at the serialization and deserialization of Bencode messages.
37 changes: 37 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Examples

## Announce peer

```sh
cargo run --example announce_peer <40 bytes hex info_hash>
```

## Get peers

```sh
cargo run --example get_peers <40 bytes hex info_hash>
```

## Put Immutable

```sh
cargo run --example put_immutable <string>
```

## Get Immutable

```sh
cargo run --example get_immutable <40 bytes hex target from put_immutable>
```

## Put Mutable

```sh
cargo run --example put_mutable <64 bytes hex secret_key> <string>
```

## Get Mutable

```sh
cargo run --example get_mutable <40 bytes hex target from put_mutable>
`````
70 changes: 70 additions & 0 deletions examples/announce_peer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use std::{str::FromStr, time::Instant};

use mainline::{Dht, Id};

use clap::Parser;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// info_hash to annouce a peer on
infohash: String,
}

fn main() {
let cli = Cli::parse();

match Id::from_str(cli.infohash.as_str()) {
Ok(infohash) => {
let dht = Dht::default();

let start = Instant::now();

println!("\nAnnouncing peer on an infohash: {} ...\n", cli.infohash);

let metadata = dht
.announce_peer(infohash, Some(6991))
.expect("announce_peer fialed");
println!(
"Announced peer in {:?} seconds",
start.elapsed().as_secs_f32()
);
let stored_at = metadata.stored_at();
println!("Stored at: {:?} nodes", stored_at.len());
for node in stored_at {
println!(" {:?}", node);
}

// You can now reannounce to the same closest nodes
// skipping the the lookup step.
//
// This time we choose to not sepcify the port, effectively
// making the port implicit to be detected by the storing node
// from the source address of the announce_peer request
//
// Uncomment the following lines to try it out:

// println!(
// "Announcing again to {:?} closest_nodes ...",
// metadata.closest_nodes().len()
// );
//
// let again = Instant::now();
// match dht.announce_peer_to(infohash, metadata.closest_nodes(), None) {
// Ok(metadata) => {
// println!(
// "Announced again to {:?} nodes in {:?} seconds",
// metadata.stored_at().len(),
// again.elapsed().as_secs()
// );
// }
// Err(err) => {
// println!("Error: {:?}", err);
// }
// }
}
Err(err) => {
println!("Error: {:?}", err)
}
};
}
63 changes: 63 additions & 0 deletions examples/get_immutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::{str::FromStr, time::Instant};

use mainline::{Dht, Id};

use clap::Parser;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Immutable data sha1 hash to lookup.
target: String,
}

fn main() {
let cli = Cli::parse();

let target_parse_result: Result<Id, _> = Id::from_str(cli.target.as_str());

match target_parse_result {
Ok(infohash) => {
let dht = Dht::default();

let start = Instant::now();

println!("\nLooking up immutable data: {} ...\n", cli.target);

let mut response = &mut dht.get_immutable(infohash);

if let Some(res) = response.next() {
println!(
"Got result in {:?} seconds\n",
start.elapsed().as_secs_f32()
);

// No need to stream responses, just print the first result, since
// all immutable data items are guaranteed to be the same.

match String::from_utf8(res.value.to_vec()) {
Ok(string) => {
println!("Got immutable data: {:?} | from: {:?}", string, res.from);
}
Err(_) => {
println!("Got immutable data: {:?} | from: {:?}", res.value, res.from);
}
};
}

println!(
"\nVisited {:?} nodes, found {:?} closest nodes",
response.visited,
&response.closest_nodes.len()
);

println!(
"\nQuery exhausted in {:?} seconds",
start.elapsed().as_secs_f32(),
);
}
Err(err) => {
println!("Error: {}", err)
}
};
}
92 changes: 92 additions & 0 deletions examples/get_mutable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use ed25519_dalek::VerifyingKey;
use std::convert::TryFrom;

use std::time::Instant;

use mainline::Dht;

use clap::Parser;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Mutable data public key.
public_key: String,
}

fn main() {
let cli = Cli::parse();

let public_key = from_hex(cli.public_key.clone());
let dht = Dht::default();

let start = Instant::now();
let mut first = false;

println!("\nLooking up mutable item: {} ...\n", cli.public_key);

let mut count = 0;

let mut response = &mut dht.get_mutable(public_key.as_bytes(), None);

for res in &mut response {
if !first {
first = true;
println!(
"Got first result in {:?} seconds\n",
start.elapsed().as_secs_f32()
);

println!("Streaming mutable items:\n");
}

count += 1;

match String::from_utf8(res.item.value().to_vec()) {
Ok(string) => {
println!(
"Got mutable item: {:?}, seq: {:?} | from: {:?}",
string,
res.item.seq(),
res.from
);
}
Err(_) => {
println!(
"Got mutable item: {:?}, seq: {:?} | from: {:?}",
res.item.value(),
res.item.seq(),
res.from
);
}
};
}

println!(
"Visited {:?} nodes, found {:?} closest nodes",
response.visited,
&response.closest_nodes.len()
);

println!(
"\nQuery exhausted in {:?} seconds, got {:?} peers.",
start.elapsed().as_secs_f32(),
count
);
}

fn from_hex(s: String) -> VerifyingKey {
if s.len() % 2 != 0 {
panic!("Number of Hex characters should be even");
}

let mut bytes = Vec::with_capacity(s.len() / 2);

for i in 0..s.len() / 2 {
let byte_str = &s[i * 2..(i * 2) + 2];
let byte = u8::from_str_radix(byte_str, 16).expect("Invalid hex character");
bytes.push(byte);
}

VerifyingKey::try_from(bytes.as_slice()).expect("Invalid mutable key")
}
Loading
Loading