Skip to content

Commit

Permalink
Peer protocol & handshake
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Oct 22, 2023
1 parent 5f28d2b commit 353e4dc
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 20 deletions.
Binary file removed data/archlinux-2023.10.14-x86_64.iso.torrent
Binary file not shown.
6 changes: 2 additions & 4 deletions src/hex.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use crate::types::ByteString;

pub fn hex(str: &ByteString) -> String {
str.iter().map(|c| format!("{:x?}", c)).collect::<String>()
pub fn hex(str: &[u8]) -> String {
str.iter().map(|c| format!("{:02x}", c)).collect::<String>()
}
26 changes: 22 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use peer::handshake;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::{fs, path::PathBuf};

Expand All @@ -6,12 +7,13 @@ use types::ByteString;

use crate::{
metainfo::Metainfo,
tracker::{tracker_request, TrackerRequest},
tracker::{tracker_request, TrackerRequest, TrackerResponse},
};

mod bencode;
mod hex;
mod metainfo;
mod peer;
mod sha1;
mod tracker;
mod types;
Expand All @@ -23,12 +25,12 @@ fn main() {
(Some(metadata), left) if left.is_empty() => metadata,
_ => panic!("metadata file parsing error"),
};
println!("{metainfo_dict:#?}");
println!("{metainfo_dict:?}");
let metainfo = match Metainfo::try_from(metainfo_dict.clone()) {
Ok(info) => info,
Err(e) => panic!("metadata file structure error: {e}"),
};
println!("{metainfo:#?}");
println!("{metainfo:?}");
let info_dict_str = match metainfo_dict {
bencode::BencodeValue::Dict(d) => d.get("info").unwrap().encode(),
_ => unreachable!(),
Expand All @@ -38,10 +40,26 @@ fn main() {
println!("peer id {}", String::from_utf8_lossy(peer_id.as_slice()));
let tracker_response = tracker_request(
metainfo.announce,
TrackerRequest::new(info_hash, peer_id, tracker::TrackerEvent::Started, None),
TrackerRequest::new(
info_hash.clone(),
peer_id.clone(),
tracker::TrackerEvent::Started,
None,
),
)
.expect("request failed");
println!("tracker response: {tracker_response:?}");
if let TrackerResponse::Success(resp) = tracker_response {
for p in resp.peers {
match handshake(&p, &info_hash, &peer_id) {
Ok(_) => {
println!("successfull handshake with peer {:?}", p);
break;
}
Err(e) => eprintln!("{}", e),
}
}
}
}

/// Generate random 20 byte string, starting with -<2 byte client name><4 byte client version>-
Expand Down
64 changes: 64 additions & 0 deletions src/peer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::io::{self, Read, Write};
use std::net::{IpAddr, SocketAddr, TcpStream};
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;

use crate::hex::hex;
use crate::tracker::TrackerPeer;
use crate::types::ByteString;

pub fn handshake(
peer: &TrackerPeer,
info_hash: &ByteString,
peer_id: &ByteString,
) -> io::Result<()> {
println!("connecting to peer {peer:?}");
let timeout = Duration::new(2, 0);
let mut stream = TcpStream::connect_timeout(
&SocketAddr::new(IpAddr::from_str(&peer.ip).unwrap(), peer.port as u16),
timeout,
)?;
stream.set_read_timeout(Some(timeout))?;
stream.set_write_timeout(Some(timeout))?;
let handshake = handshake_packet(info_hash, peer_id);
println!("writing handshake {}", hex(&handshake.to_vec()));
match stream.write_all(&handshake) {
Err(e) => {
eprintln!("write error: {}", e);
return Err(e);
}
_ => println!("write ok"),
};
stream.flush()?;
let mut reader = stream;
let mut read_packet = vec![];
println!("reading response");
let mut retry = 0;
loop {
if retry > 3 {
return Err(io::Error::new(io::ErrorKind::Other, "read timeout"));
};
match reader.read_to_end(&mut read_packet) {
Err(e) => {
eprintln!("read error: {}", e);
return Err(e);
}
Ok(n) if n > 0 => {
println!("peer response: {}", hex(&read_packet.to_vec()));
}
_ => {
println!("no data");
retry += 1;
sleep(Duration::new(1, 0));
}
};
}
}

pub fn handshake_packet(info_hash: &ByteString, peer_id: &ByteString) -> Vec<u8> {
let pstr = "BitTorrent protocol";
let pstrlen = &[pstr.len() as u8];
let reserved = &[0u8; 8];
[pstrlen, pstr.as_bytes(), reserved, &info_hash, &peer_id].concat()
}
25 changes: 13 additions & 12 deletions src/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,19 @@ impl TryFrom<BencodeValue> for TrackerResponse {
#[allow(dead_code)]
#[derive(Debug)]
pub struct TrackerResponseSuccess {
peers: Vec<TrackerPeer>,
interval: i64,
warning_message: Option<String>,
min_interval: Option<i64>,
tracker_id: Option<ByteString>,
complete: Option<i64>,
incomplete: Option<i64>,
pub peers: Vec<TrackerPeer>,
pub interval: i64,
pub warning_message: Option<String>,
pub min_interval: Option<i64>,
pub tracker_id: Option<ByteString>,
pub complete: Option<i64>,
pub incomplete: Option<i64>,
}

pub struct TrackerPeer {
peer_id: ByteString,
ip: String,
port: i64,
pub peer_id: ByteString,
pub ip: String,
pub port: i64,
}

impl fmt::Debug for TrackerPeer {
Expand Down Expand Up @@ -217,9 +217,10 @@ pub fn tracker_request(
let resp = Client::new()
.get(format!("{announce}{query}"))
.send()
.map_err(|e| e.to_string())?
.map_err(|e| format!("request error: {}", e))?
.bytes()
.map_err(|e| e.to_string())?;
.map_err(|e| format!("request body error: {}", e))?;
println!("{}", String::from_utf8_lossy(&resp));
let resp_dict = parse_bencoded(resp.to_vec())
.0
.ok_or("Malformed response")?;
Expand Down

0 comments on commit 353e4dc

Please sign in to comment.