Skip to content

Instantly share code, notes, and snippets.

@GGist
Created December 22, 2015 08:13
Show Gist options
  • Save GGist/19a17c954dac46c6a5da to your computer and use it in GitHub Desktop.
Save GGist/19a17c954dac46c6a5da to your computer and use it in GitHub Desktop.
Command Line DHT Search Example (bip_dht)
[package]
name = "testing"
version = "0.1.0"
[dependencies]
bip_dht = "0.2.0"
extern crate bip_dht;
use std::collections::{HashSet};
use std::io::{self, BufRead, Write};
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr};
use std::thread::{self};
use bip_dht::{Handshaker, Router, DhtBuilder,
InfoHash, PeerId, DhtEvent};
// Mock handshaker object. Wont actually listen for TCP connections or initiate connections,
// rather, it will print out the unique addresses that our DHT finds for us to connect to.
struct MockHandshaker {
new_peer_filter: HashSet<SocketAddr>
}
impl MockHandshaker {
fn new() -> MockHandshaker {
MockHandshaker{ new_peer_filter: HashSet::new() }
}
}
impl Handshaker for MockHandshaker {
type Stream = ();
fn id(&self) -> PeerId {
[0u8; 20].into()
}
fn port(&self) -> u16 {
// This is the port that will be persisted to the DHT when we announce ourselves. In other words,
// this is the port we will receive handshakes on that are initiated by a peer wishing to connect
// to us. This port does NOT have to be the same as our DHT source listen port, but it can be.
6881
}
fn connect(&mut self, _: Option<PeerId>, _: InfoHash, addr: SocketAddr) {
// We will only print NEW peers that we see, otherwise we will be printing a lot
// of duplicate peers for one, or even multiple searches with the same Handshake object.
if self.new_peer_filter.contains(&addr) {
return
}
println!("Found NEW peer: {:?}, Total Peers Found: {:?}", addr, self.new_peer_filter.len());
self.new_peer_filter.insert(addr);
}
fn filter<F>(&mut self, _: Box<F>)
where F: Fn(SocketAddr) -> bool + Send {
()
}
fn stream(&self, _: InfoHash) -> () {
()
}
}
fn main() {
let src_ip = Ipv4Addr::new(0, 0, 0, 0);
let src_addr = SocketAddr::V4(SocketAddrV4::new(src_ip, 6881));
// Starts up the DHT with 3 routers, explicitly sets our source address (not required),
// and changes read only from true (default) to false, allowing us to respond to requests
// instead of ignoring them (slightly more network traffic).
let dht = DhtBuilder::with_router(Router::uTorrent)
.add_router(Router::BitTorrent).add_router(Router::Transmission)
.set_source_addr(src_addr).set_read_only(false)
.start_mainline(MockHandshaker::new()).unwrap();
let dht_events = dht.events();
// Spin up a new thread to listen for user input for InfoHash searches.
thread::spawn(move || {
let prompt = "Enter an info hash to search for peers for (or nothing to exit): ";
let stdin = io::stdin();
let stdin_lock = stdin.lock();
print!("{}", prompt);
io::stdout().flush().unwrap();
for line in stdin_lock.lines() {
let u_line = line.unwrap();
if u_line.is_empty() {
// Exit the thread
break;
}
// Check if a valid hex string (info hash) was entered
match hex_string_to_bytes(&u_line) {
Some(bytes) => dht.search(bytes.into(), false),
None => println!("Entered an invalid info hash...")
};
print!("{}", prompt);
io::stdout().flush().unwrap();
}
// Since we moved the MainlineDht object in this thread, exiting this thread will
// cause it to be dropped which will shut down our DHT and since a ShuttingDown
// event will get generated, the main thread will exit.
});
// In the main thread, report DHT events to us. In particular, the BootstrapComplete
// event will tell us that the DHT started up successfully (should take ~20 seconds).
// Any tasks executed before the BootstrapComplete event will be queued and executed
// after that event.
//
// If we receivie a ShuttingDown event, it means something went wrong causing the DHT
// to shut down. In that case, exit the program.
for event in dht_events.iter() {
println!("\nReceived a DHT event: {:?}", event);
if let DhtEvent::ShuttingDown(_) = event {
break;
}
}
}
// Convert user inputed hex strings into their byte array equivalent.
fn hex_string_to_bytes(hex_string: &str) -> Option<[u8; 20]> {
if hex_string.chars().count() != 40 {
None
} else {
let mut index = 0;
let mut bytes = [0u8; 20];
while index < 40 {
let mut chars = hex_string.chars().skip(index);
let (high, low) = (chars.next().unwrap(), chars.next().unwrap());
// Convert the character represenation into its decimal equivalent.
let dec_high = if high.is_alphabetic() {
(high.to_lowercase().next().unwrap() as u8 - 'a' as u8) + 10
} else {
high as u8 - b'0'
};
let dec_low = if low.is_alphabetic() {
(low.to_lowercase().next().unwrap() as u8 - 'a' as u8) + 10
} else {
low as u8 - b'0'
};
// Validate the bounds of the hex decimal values and return the byte.
let byte = match (dec_high, dec_low) {
(0...15, 0...15) => (dec_high << 4) + dec_low,
_ => return None
};
// Put the bytes into it's place.
bytes[index / 2] = byte;
index += 2;
}
Some(bytes)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment