You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

798 lines
25 KiB
Rust

use crate::krpc::{Message, MessageBody, RequestBody, ResponseBody, Want};
use crate::Table;
use rand::random;
use serde_derive::{Deserialize, Serialize};
use std::collections::{HashSet, VecDeque};
use std::convert::TryInto;
use std::net::{IpAddr, SocketAddr};
use std::ops::Add;
use std::time::{Duration, Instant};
use torment_core::infohash::v1::U160;
use torment_core::ip::IpAddrExt;
use torment_core::peer_storage::{PeerStorageHolder, PeerStorageSource};
use torment_core::utils::{EphemeralMap, EphemeralSet};
use torment_core::{ContactInfo, LookupFilter};
#[derive(Debug)]
enum OpenRequest {
FindNode { target: U160 },
GetPeers { info_hash: U160 },
Ping,
Announce { info_hash: U160 },
}
const BOOTSTRAP_INTERVAL: Duration = Duration::from_secs(60);
#[derive(Debug)]
pub struct HostNode {
id: U160,
port: Option<u16>,
bootstrap_nodes: HashSet<(SocketAddr, Option<Want>)>,
last_bootstrap: Option<Instant>,
ipv4_table: Table,
ipv6_table: Table,
nodes: EphemeralMap<(U160, IpAddr, u16), PeerNode>,
queue: VecDeque<(Message, SocketAddr)>,
peer_storage: PeerStorageHolder,
}
#[derive(Debug)]
struct PeerNode {
id: U160,
addr: SocketAddr,
transactions: EphemeralMap<u16, OpenRequest>,
pre_announced: EphemeralSet<U160>,
last_activity: Instant,
tokens: EphemeralSet<u64>,
received_tokens: EphemeralSet<Vec<u8>>,
transaction_id: u16,
}
#[derive(Serialize, Deserialize)]
pub struct PersistentState {
id: U160,
nodes: Vec<ContactInfo>,
nodes6: Vec<ContactInfo>,
}
fn validate_ip<T: Into<IpAddr>>(ip: T) -> bool {
IpAddrExt::ext_is_global(&ip.into())
}
impl PeerNode {
fn clean(&mut self) {
self.transactions.clean();
self.tokens.clean();
self.received_tokens.clean();
self.pre_announced.clean();
}
fn contact_info(&self) -> ContactInfo {
ContactInfo {
id: self.id,
contact: self.addr,
}
}
}
impl PeerNode {
fn create<T: Into<SocketAddr>>(id: U160, addr: T) -> PeerNode {
PeerNode {
id,
addr: addr.into(),
transactions: EphemeralMap::new(),
pre_announced: Default::default(),
last_activity: Instant::now(),
tokens: Default::default(),
received_tokens: Default::default(),
transaction_id: 0,
}
}
}
impl HostNode {
pub fn new(peer_storage: PeerStorageHolder, port: Option<u16>) -> HostNode {
let id = U160::random();
HostNode {
id,
port,
bootstrap_nodes: Default::default(),
last_bootstrap: None,
ipv4_table: Table::new_with_id(id),
ipv6_table: Table::new_with_id(id),
nodes: EphemeralMap::new(),
queue: Default::default(),
peer_storage,
}
}
pub fn from_persistent_state(
peer_storage: PeerStorageHolder,
port: Option<u16>,
state: PersistentState,
) -> HostNode {
let mut host_node = HostNode {
id: state.id,
port,
bootstrap_nodes: Default::default(),
last_bootstrap: None,
ipv4_table: Table::new_with_id(state.id),
ipv6_table: Table::new_with_id(state.id),
nodes: EphemeralMap::new(),
queue: Default::default(),
peer_storage,
};
for node in state.nodes {
host_node
.ipv4_table
.add_node(node.id, node.contact.ip(), node.contact.port());
}
for node in state.nodes6 {
host_node
.ipv6_table
.add_node(node.id, node.contact.ip(), node.contact.port());
}
host_node.update_tables();
host_node
}
fn get_next_t(&mut self, contact_info: &ContactInfo) -> [u8; 2] {
let peer_node = self.get_peer_node(contact_info.id, contact_info.contact);
let old_id = peer_node.transaction_id;
peer_node.transaction_id = peer_node.transaction_id.overflowing_add(1).0;
old_id.to_ne_bytes()
}
pub fn process(&mut self, message: Message, from: SocketAddr) {
match message.body {
MessageBody::Request(ref req) => self.handle_request(req, message.transaction_id, from),
MessageBody::Response(ref resp) => {
self.handle_response(resp, message.transaction_id, from)
}
_ => {}
}
}
pub fn add_bootstrap(&mut self, socket_addr: SocketAddr, want: Option<Want>) {
self.bootstrap_nodes.insert((socket_addr, want));
}
fn get_k_closest_nodes(&self, id: U160, want: Option<Want>) -> Vec<ContactInfo> {
let mut nodes = vec![];
if want.unwrap_or(Want::N4) == Want::N4 {
nodes.extend(self.ipv4_table.find_k_closest_nodes(id, None))
}
if want.unwrap_or(Want::N6) == Want::N6 {
nodes.extend(self.ipv6_table.find_k_closest_nodes(id, None))
}
nodes
}
fn find_node(&mut self, target: U160, want: Option<Want>) {
if want.unwrap_or(Want::N4) == Want::N4 && self.ipv4_table.find_node(target).is_some() {
// noop
return;
}
if want.unwrap_or(Want::N6) == Want::N6 && self.ipv6_table.find_node(target).is_some() {
// noop
return;
}
for node in self.get_k_closest_nodes(target, want) {
let t = self.get_next_t(&node);
self.find_node_at(t, node.contact, target, want);
self.get_peer_node(node.id, node.contact)
.transactions
.insert(
u16::from_ne_bytes(t),
OpenRequest::FindNode { target },
Instant::now().add(Duration::from_secs(5 * 60)),
);
self.notify_table_of_outgoing_activity(&node);
}
}
fn find_node_at(
&mut self,
t: [u8; 2],
socket_addr: SocketAddr,
target: U160,
want: Option<Want>,
) {
let message = Message::request_find_node(self.id, t, target, want);
self.queue.push_back((message, socket_addr))
}
fn get_peers(&mut self, info_hash: U160, want: Option<Want>) {
let nodes = self.get_k_closest_nodes(info_hash, want);
for node in nodes {
self.get_peers_from(&node, info_hash, want)
}
}
fn get_peers_from(&mut self, node: &ContactInfo, info_hash: U160, want: Option<Want>) {
let message = Message::request_get_peers(self.id, self.get_next_t(&node), info_hash, want);
self.get_peer_node(node.id, node.contact)
.transactions
.insert(
u16::from_ne_bytes(message.transaction_id),
OpenRequest::GetPeers { info_hash },
Instant::now().add(Duration::from_secs(5 * 60)),
);
self.notify_table_of_outgoing_activity(node);
self.queue.push_back((message, node.contact))
}
fn notify_table_of_outgoing_activity(&mut self, node: &ContactInfo) {
if node.contact.is_ipv4() {
self.ipv4_table.handle_outgoing_activity(
node.id,
node.contact.ip(),
node.contact.port(),
);
} else {
self.ipv6_table.handle_outgoing_activity(
node.id,
node.contact.ip(),
node.contact.port(),
);
}
}
fn announce_peer_to(&mut self, node: &ContactInfo, info_hash: U160, token: Vec<u8>) {
let message = Message::request_announce_peer(
self.id,
self.get_next_t(&node),
info_hash,
token,
self.port.unwrap_or(0),
self.port.is_none(),
);
self.get_peer_node(node.id, node.contact)
.transactions
.insert(
u16::from_ne_bytes(message.transaction_id),
OpenRequest::Announce { info_hash },
Instant::now().add(Duration::from_secs(5 * 60)),
);
self.notify_table_of_outgoing_activity(node);
self.queue.push_back((message, node.contact))
}
pub fn ping(&mut self, node: &ContactInfo) {
let msg = Message::request_ping(self.id, self.get_next_t(node));
self.get_peer_node(node.id, node.contact)
.transactions
.insert(
u16::from_ne_bytes(msg.transaction_id),
OpenRequest::Ping,
Instant::now().add(Duration::from_secs(5 * 60)),
);
self.notify_table_of_outgoing_activity(node);
self.queue.push_back((msg, node.contact))
}
fn announce_peer(&mut self, info_hash: U160) {
let mut announce_to = vec![];
let mut peers_from = vec![];
for (_, details) in &mut self.nodes.iter_mut() {
if let Some(token) = details.received_tokens.remove_first() {
announce_to.push((details.contact_info(), token))
} else {
details
.pre_announced
.insert(info_hash, Instant::now().add(Duration::from_secs(5 * 60)));
peers_from.push(details.contact_info());
}
}
for (node, token) in announce_to {
self.announce_peer_to(&node, info_hash, token)
}
for node in peers_from {
self.get_peers_from(&node, info_hash, None)
}
}
fn has_node(&self, node: &ContactInfo) -> bool {
self.ipv4_table.has_node(node) || self.ipv6_table.has_node(node)
}
fn knows_node(&self, node: &ContactInfo) -> bool {
self.nodes
.contains_key(&(node.id, node.contact.ip(), node.contact.port()))
}
fn handle_response(&mut self, response: &ResponseBody, t: [u8; 2], from: SocketAddr) {
let from_id = response.node_id();
if from_id == self.id {
// In this house we don't talk to imposters
return;
}
match response {
ResponseBody::GetPeers(get_peers) => {
let node = self.get_peer_node(from_id, from);
let info_hash = if let Some(OpenRequest::GetPeers { info_hash }) =
node.transactions.remove_value(&u16::from_ne_bytes(t))
{
info_hash
} else {
return;
};
let is_pre_announce = if node.pre_announced.remove(&info_hash) {
true
} else {
node.received_tokens.insert(
get_peers.token.clone(),
Instant::now().add(Duration::from_secs(5 * 60)),
);
false
};
if is_pre_announce {
let contact_info = node.contact_info();
self.announce_peer_to(&contact_info, info_hash, get_peers.token.clone());
return;
}
if get_peers.values.len() > 0 {
self.peer_storage.add_peers(
info_hash,
get_peers.values.clone(),
PeerStorageSource::DHT,
);
}
for node in &get_peers.nodes.nodes {
if !validate_ip(node.contact.ip()) {
continue;
}
if self.has_node(node) {
continue;
}
self.ipv4_table
.add_node(node.id, node.contact.ip(), node.contact.port());
self.get_peers_from(node, info_hash, None);
}
for node in &get_peers.nodes.nodes6 {
if !validate_ip(node.contact.ip()) {
continue;
}
if self.has_node(node) {
continue;
}
self.ipv6_table
.add_node(node.id, node.contact.ip(), node.contact.port());
self.get_peers_from(node, info_hash, None);
}
}
ResponseBody::FindNode(find_node) => {
let node = self.get_peer_node(response.node_id(), from);
// We don't really care if we had a transaction or not, since at bootstrap we didn't know the nodes ID yet
let request = node.transactions.remove_value(&u16::from_ne_bytes(t));
let target = if let Some(OpenRequest::FindNode { target }) = request {
target
} else {
self.id
};
let poke_if_closer = |host: &mut HostNode, node: &ContactInfo| {
if !host.knows_node(node) && (node.id ^ host.id) < (from_id ^ host.id) {
let t = host.get_next_t(node);
host.find_node_at(t, node.contact, target, None);
}
};
for node in &find_node.nodes {
if !validate_ip(node.contact.ip()) {
continue;
}
if self
.ipv4_table
.add_node(node.id, node.contact.ip(), node.contact.port())
{
poke_if_closer(self, node);
}
}
for node in &find_node.nodes6 {
if !validate_ip(node.contact.ip()) {
continue;
}
if self
.ipv6_table
.add_node(node.id, node.contact.ip(), node.contact.port())
{
poke_if_closer(self, node);
}
}
}
ResponseBody::Empty(id) => {
let node = self.get_peer_node(response.node_id(), from);
node.transactions.remove_value(&u16::from_ne_bytes(t));
if node.addr.is_ipv4() {
self.ipv4_table.handle_activity(*id, from.ip(), from.port());
} else {
self.ipv6_table.handle_activity(*id, from.ip(), from.port());
}
}
}
}
fn get_peer_node<T: Into<SocketAddr>>(&mut self, id: U160, addr: T) -> &mut PeerNode {
let addr = addr.into();
self.nodes.update_expiry(
&(id, addr.ip(), addr.port()),
Instant::now().add(Duration::from_secs(60 * 15)),
);
self.nodes
.entry((id, addr.ip(), addr.port()))
.or_insert_with(Instant::now().add(Duration::from_secs(60 * 15)), || {
PeerNode::create(id, addr)
})
}
fn create_token(&mut self, id: U160, addr: SocketAddr, _info_hash: U160) -> Vec<u8> {
let token: u64 = random();
self.get_peer_node(id, addr).tokens.insert(
token.clone(),
Instant::now().add(Duration::from_secs(60 * 5)),
);
token.to_ne_bytes().to_vec()
}
fn handle_request(&mut self, request: &RequestBody, t: [u8; 2], from: SocketAddr) {
if request.node_id() == self.id {
// In this house we don't talk to imposters
return;
}
let contact_info = ContactInfo {
id: request.node_id(),
contact: from,
};
if !self.has_node(&contact_info) {
if from.is_ipv4() {
self.ipv4_table.add_incoming_node(
contact_info.id,
contact_info.contact.ip(),
contact_info.contact.port(),
);
} else {
self.ipv6_table.add_incoming_node(
contact_info.id,
contact_info.contact.ip(),
contact_info.contact.port(),
);
}
}
match request {
RequestBody::GetPeers(get_peers) => {
let table =
get_peers
.want
.unwrap_or_else(|| if from.is_ipv4() { Want::N4 } else { Want::N6 });
let peers = self.peer_storage.get_peers(
get_peers.info_hash,
if table == Want::N4 {
LookupFilter::IPv4
} else {
LookupFilter::IPv6
},
);
let token = self.create_token(get_peers.id, from, get_peers.info_hash);
if peers.len() == 0 {
self.queue.push_back((
Message::response_get_peers_not_found(
self.id,
t,
token,
if table == Want::N4 {
self.ipv4_table.find_k_closest_nodes(
get_peers.info_hash,
Some((get_peers.id, from.ip(), from.port())),
)
} else {
vec![]
},
if table == Want::N6 {
self.ipv6_table.find_k_closest_nodes(
get_peers.info_hash,
Some((get_peers.id, from.ip(), from.port())),
)
} else {
vec![]
},
),
from,
))
} else {
self.queue.push_back((
Message::response_get_peers_found(self.id, t, token, peers),
from,
));
}
}
RequestBody::FindNode(find_node) => {
let table =
find_node
.want
.unwrap_or_else(|| if from.is_ipv4() { Want::N4 } else { Want::N6 });
let nodes = if table == Want::N4 {
if let Some(node) = self.ipv4_table.find_node(find_node.target) {
vec![node]
} else {
self.ipv4_table.find_k_closest_nodes(
find_node.target,
Some((find_node.id, from.ip(), from.port())),
)
}
} else {
vec![]
};
let nodes6 = if table == Want::N6 {
if let Some(node) = self.ipv6_table.find_node(find_node.target) {
vec![node]
} else {
self.ipv6_table.find_k_closest_nodes(
find_node.target,
Some((find_node.id, from.ip(), from.port())),
)
}
} else {
vec![]
};
self.queue
.push_back((Message::response_find_node(self.id, t, nodes, nodes6), from))
}
RequestBody::Ping(id) => {
if from.is_ipv4() {
self.ipv4_table.handle_activity(*id, from.ip(), from.port())
} else {
self.ipv6_table.handle_activity(*id, from.ip(), from.port())
}
self.queue
.push_back((Message::response_empty(self.id, t), from))
}
RequestBody::AnnouncePeer(announce) => {
let ok = if let Some(node) =
self.nodes.get_mut(&(announce.id, from.ip(), from.port()))
{
if announce.token.len() != 8 {
false
} else {
node.tokens.remove(&u64::from_ne_bytes(
announce.token[0..8].try_into().unwrap(),
))
}
} else {
false
};
if !ok {
self.queue
.push_back((Message::error(t, 203, "Bad token".to_string()), from))
} else {
self.peer_storage.add_peers(
announce.info_hash,
vec![SocketAddr::new(
from.ip(),
if announce.implied_port {
from.port()
} else {
announce.port
},
)],
PeerStorageSource::DHT,
);
self.queue
.push_back((Message::response_empty(self.id, t), from))
}
}
}
}
pub fn next(&mut self) -> Option<(Message, SocketAddr)> {
self.queue.pop_front()
}
fn clean(&mut self) {
for (_, node) in self.nodes.iter_mut() {
node.clean();
}
}
pub fn housekeeping(&mut self) {
self.clean();
self.update_tables();
if self
.last_bootstrap
.map(|last| last.add(BOOTSTRAP_INTERVAL) < Instant::now())
.unwrap_or(true)
{
for (addr, want) in self.bootstrap_nodes.clone() {
self.find_node_at(random(), addr, self.id, want);
}
self.last_bootstrap = Some(Instant::now());
}
}
fn update_tables(&mut self) {
self.ipv4_table.update_nodes();
self.ipv6_table.update_nodes();
for item in self.ipv6_table.get_silent_nodes() {
self.ping(&item);
}
for item in self.ipv4_table.get_silent_nodes() {
self.ping(&item);
}
}
pub fn num_tracking_nodes(&self) -> usize {
self.nodes.len()
}
pub fn num_ipv4_table_nodes(&self) -> usize {
self.ipv4_table.count_nodes()
}
pub fn num_ipv6_table_nodes(&self) -> usize {
self.ipv6_table.count_nodes()
}
}
#[cfg(test)]
mod test {
use crate::host_node::HostNode;
use crate::krpc::Message;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use torment_core::infohash::v1::U160;
use torment_core::peer_storage::{PeerStorage, PeerStorageHolder};
use torment_core::ContactInfo;
fn get_host() -> HostNode {
let peer_storage = PeerStorageHolder::new(PeerStorage::new());
HostNode::new(peer_storage, None)
}
#[test]
fn ping_pong() {
let mut host = get_host();
let t = [0u8; 2];
let ip = Ipv4Addr::LOCALHOST;
let id = U160::random();
let from = SocketAddr::new(IpAddr::from(ip), 4);
host.process(Message::request_ping(id, t), from);
assert_eq!(1, host.ipv4_table.count_nodes());
assert_eq!(
host.next(),
Some((Message::response_empty(host.id, t), from))
)
}
#[test]
fn get_peers() {
let mut host = get_host();
let t = [0u8; 2];
let ip = Ipv4Addr::LOCALHOST;
let from = SocketAddr::new(IpAddr::from(ip), 4);
let id = U160::random();
host.process(Message::request_ping(id, t), from);
assert_eq!(1, host.ipv4_table.count_nodes());
host.next();
let other = U160::random();
host.process(Message::request_ping(other, t), from);
assert_eq!(2, host.ipv4_table.count_nodes());
host.next();
let info_hash = U160::random();
host.process(Message::request_get_peers(id, t, info_hash, None), from);
let nodes = host
.next()
.unwrap()
.0
.expect_response()
.1
.expect_get_peers()
.nodes
.nodes;
assert_eq!(1, nodes.len());
assert_eq!(
&ContactInfo {
id: other,
contact: SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 4),
},
nodes.get(0).unwrap()
);
}
#[test]
fn announce_peer() {
let mut host = get_host();
let t = [0u8; 2];
let ip = Ipv4Addr::LOCALHOST;
let from = SocketAddr::new(IpAddr::from(ip), 4);
let id = U160::random();
host.process(Message::request_ping(id, t), from);
assert_eq!(1, host.ipv4_table.count_nodes());
host.next();
let other = U160::random();
host.process(Message::request_ping(other, t), from);
assert_eq!(2, host.ipv4_table.count_nodes());
host.next();
let info_hash = U160::random();
host.process(Message::request_get_peers(other, t, info_hash, None), from);
let resp = host
.next()
.unwrap()
.0
.expect_response()
.1
.expect_get_peers();
host.process(
Message::request_announce_peer(other, t, info_hash, resp.token.clone(), 4, false),
from,
);
host.next().unwrap().0.expect_response().1.expect_empty();
host.process(
Message::request_announce_peer(other, t, info_hash, resp.token, 4, false),
from,
);
host.next().unwrap().0.expect_error();
}
}