Skip to content

Commit afc4731

Browse files
committed
feat: [#625] a new UDP tracker client
You can use it with: ```console cargo run --bin udp_tracker_client 144.126.245.19:6969 9c38422213e30bff212b30c360d26f9a02136422 ``` and the output should be something like: ``` AnnounceIpv4( AnnounceResponse { transaction_id: TransactionId( -888840697, ), announce_interval: AnnounceInterval( 300, ), leechers: NumberOfPeers( 0, ), seeders: NumberOfPeers( 4, ), peers: [ ResponsePeer { ip_address: xx.yy.zz.254, port: Port( 51516, ), }, ResponsePeer { ip_address: xx.yy.zz.20, port: Port( 59448, ), }, ResponsePeer { ip_address: xx.yy.zz.224, port: Port( 58587, ), }, ], }, ) ```
1 parent 203ce96 commit afc4731

File tree

3 files changed

+185
-3
lines changed

3 files changed

+185
-3
lines changed

src/bin/udp_tracker_client.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
use std::env;
2+
use std::net::{Ipv4Addr, SocketAddr};
3+
use std::str::FromStr;
4+
5+
use aquatic_udp_protocol::common::InfoHash;
6+
use aquatic_udp_protocol::{
7+
AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, Port, Response,
8+
TransactionId,
9+
};
10+
use log::{debug, LevelFilter};
11+
use torrust_tracker::shared::bit_torrent::info_hash::InfoHash as TorrustInfoHash;
12+
use torrust_tracker::shared::bit_torrent::tracker::udp::client::{UdpClient, UdpTrackerClient};
13+
14+
const ASSIGNED_BY_OS: i32 = 0;
15+
const RANDOM_TRANSACTION_ID: i32 = -888_840_697;
16+
17+
#[tokio::main]
18+
async fn main() {
19+
setup_logging(LevelFilter::Info);
20+
21+
let (remote_socket_addr, info_hash) = parse_arguments();
22+
23+
// Configuration
24+
let local_port = ASSIGNED_BY_OS;
25+
let transaction_id = RANDOM_TRANSACTION_ID;
26+
let bind_to = format!("0.0.0.0:{local_port}");
27+
28+
// Bind to local port
29+
30+
debug!("Binding to: {bind_to}");
31+
let udp_client = UdpClient::bind(&bind_to).await;
32+
let bound_to = udp_client.socket.local_addr().unwrap();
33+
debug!("Bound to: {bound_to}");
34+
35+
// Connect to remote socket
36+
37+
debug!("Connecting to remote: udp://{remote_socket_addr}");
38+
udp_client.connect(&remote_socket_addr).await;
39+
40+
let udp_tracker_client = UdpTrackerClient { udp_client };
41+
42+
let transaction_id = TransactionId(transaction_id);
43+
44+
let connection_id = send_connection_request(transaction_id, &udp_tracker_client).await;
45+
46+
let response = send_announce_request(
47+
connection_id,
48+
transaction_id,
49+
info_hash,
50+
Port(bound_to.port()),
51+
&udp_tracker_client,
52+
)
53+
.await;
54+
55+
println!("{response:#?}");
56+
}
57+
58+
fn setup_logging(level: LevelFilter) {
59+
if let Err(_err) = fern::Dispatch::new()
60+
.format(|out, message, record| {
61+
out.finish(format_args!(
62+
"{} [{}][{}] {}",
63+
chrono::Local::now().format("%+"),
64+
record.target(),
65+
record.level(),
66+
message
67+
));
68+
})
69+
.level(level)
70+
.chain(std::io::stdout())
71+
.apply()
72+
{
73+
panic!("Failed to initialize logging.")
74+
}
75+
76+
debug!("logging initialized.");
77+
}
78+
79+
fn parse_arguments() -> (String, TorrustInfoHash) {
80+
let args: Vec<String> = env::args().collect();
81+
82+
if args.len() != 3 {
83+
eprintln!("Error: invalid number of arguments!");
84+
eprintln!("Usage: cargo run --bin udp_tracker_client <UDP_TRACKER_SOCKET_ADDRESS> <INFO_HASH>");
85+
eprintln!("Example: cargo run --bin udp_tracker_client 144.126.245.19:6969 9c38422213e30bff212b30c360d26f9a02136422");
86+
std::process::exit(1);
87+
}
88+
89+
let remote_socket_addr = &args[1];
90+
let _valid_socket_addr = remote_socket_addr.parse::<SocketAddr>().unwrap_or_else(|_| {
91+
panic!(
92+
"Invalid argument: `{}`. Argument 1 should be a valid socket address. For example: `144.126.245.19:6969`.",
93+
args[1]
94+
)
95+
});
96+
let info_hash = TorrustInfoHash::from_str(&args[2]).unwrap_or_else(|_| {
97+
panic!(
98+
"Invalid argument: `{}`. Argument 2 should be a valid infohash. For example: `9c38422213e30bff212b30c360d26f9a02136422`.",
99+
args[2]
100+
)
101+
});
102+
103+
(remote_socket_addr.to_string(), info_hash)
104+
}
105+
106+
async fn send_connection_request(transaction_id: TransactionId, client: &UdpTrackerClient) -> ConnectionId {
107+
debug!("Sending connection request with transaction id: {transaction_id:#?}");
108+
109+
let connect_request = ConnectRequest { transaction_id };
110+
111+
client.send(connect_request.into()).await;
112+
113+
let response = client.receive().await;
114+
115+
debug!("connection request response:\n{response:#?}");
116+
117+
match response {
118+
Response::Connect(connect_response) => connect_response.connection_id,
119+
_ => panic!("error connecting to udp server. Unexpected response"),
120+
}
121+
}
122+
123+
async fn send_announce_request(
124+
connection_id: ConnectionId,
125+
transaction_id: TransactionId,
126+
info_hash: TorrustInfoHash,
127+
port: Port,
128+
client: &UdpTrackerClient,
129+
) -> Response {
130+
debug!("Sending announce request with transaction id: {transaction_id:#?}");
131+
132+
let announce_request = AnnounceRequest {
133+
connection_id,
134+
transaction_id,
135+
info_hash: InfoHash(info_hash.bytes()),
136+
peer_id: PeerId(*b"-qB00000000000000001"),
137+
bytes_downloaded: NumberOfBytes(0i64),
138+
bytes_uploaded: NumberOfBytes(0i64),
139+
bytes_left: NumberOfBytes(0i64),
140+
event: AnnounceEvent::Started,
141+
ip_address: Some(Ipv4Addr::new(0, 0, 0, 0)),
142+
key: PeerKey(0u32),
143+
peers_wanted: NumberOfPeers(1i32),
144+
port,
145+
};
146+
147+
client.send(announce_request.into()).await;
148+
149+
let response = client.receive().await;
150+
151+
debug!("announce request response:\n{response:#?}");
152+
153+
response
154+
}

src/shared/bit_torrent/tracker/udp/client.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::sync::Arc;
44
use std::time::Duration;
55

66
use aquatic_udp_protocol::{ConnectRequest, Request, Response, TransactionId};
7+
use log::debug;
78
use tokio::net::UdpSocket;
89
use tokio::time;
910

@@ -19,7 +20,12 @@ impl UdpClient {
1920
///
2021
/// Will panic if the local address can't be bound.
2122
pub async fn bind(local_address: &str) -> Self {
22-
let socket = UdpSocket::bind(local_address).await.unwrap();
23+
let valid_socket_addr = local_address
24+
.parse::<SocketAddr>()
25+
.unwrap_or_else(|_| panic!("{local_address} is not a valid socket address"));
26+
27+
let socket = UdpSocket::bind(valid_socket_addr).await.unwrap();
28+
2329
Self {
2430
socket: Arc::new(socket),
2531
}
@@ -29,7 +35,14 @@ impl UdpClient {
2935
///
3036
/// Will panic if can't connect to the socket.
3137
pub async fn connect(&self, remote_address: &str) {
32-
self.socket.connect(remote_address).await.unwrap();
38+
let valid_socket_addr = remote_address
39+
.parse::<SocketAddr>()
40+
.unwrap_or_else(|_| panic!("{remote_address} is not a valid socket address"));
41+
42+
match self.socket.connect(valid_socket_addr).await {
43+
Ok(()) => debug!("Connected successfully"),
44+
Err(e) => panic!("Failed to connect: {e:?}"),
45+
}
3346
}
3447

3548
/// # Panics
@@ -39,6 +52,8 @@ impl UdpClient {
3952
/// - Can't write to the socket.
4053
/// - Can't send data.
4154
pub async fn send(&self, bytes: &[u8]) -> usize {
55+
debug!(target: "UDP client", "send {bytes:?}");
56+
4257
self.socket.writable().await.unwrap();
4358
self.socket.send(bytes).await.unwrap()
4459
}
@@ -50,8 +65,15 @@ impl UdpClient {
5065
/// - Can't read from the socket.
5166
/// - Can't receive data.
5267
pub async fn receive(&self, bytes: &mut [u8]) -> usize {
68+
debug!(target: "UDP client", "receiving ...");
69+
5370
self.socket.readable().await.unwrap();
54-
self.socket.recv(bytes).await.unwrap()
71+
72+
let size = self.socket.recv(bytes).await.unwrap();
73+
74+
debug!(target: "UDP client", "{size} bytes received {bytes:?}");
75+
76+
size
5577
}
5678
}
5779

@@ -73,6 +95,8 @@ impl UdpTrackerClient {
7395
///
7496
/// Will panic if can't write request to bytes.
7597
pub async fn send(&self, request: Request) -> usize {
98+
debug!(target: "UDP tracker client", "send request {request:?}");
99+
76100
// Write request into a buffer
77101
let request_buffer = vec![0u8; MAX_PACKET_SIZE];
78102
let mut cursor = Cursor::new(request_buffer);
@@ -99,6 +123,8 @@ impl UdpTrackerClient {
99123

100124
let payload_size = self.udp_client.receive(&mut response_buffer).await;
101125

126+
debug!(target: "UDP tracker client", "received {payload_size} bytes. Response {response_buffer:?}");
127+
102128
Response::from_bytes(&response_buffer[..payload_size], true).unwrap()
103129
}
104130
}

tests/servers/udp/contract.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ mod receiving_an_announce_request {
118118

119119
let response = client.receive().await;
120120

121+
println!("test response {response:?}");
122+
121123
assert!(is_ipv4_announce_response(&response));
122124
}
123125
}

0 commit comments

Comments
 (0)