1
- use std:: env;
1
+ //! UDP Tracker client:
2
+ //!
3
+ //! Examples:
4
+ //!
5
+ //! Announce request:
6
+ //!
7
+ //! ```text
8
+ //! cargo run --bin udp_tracker_client 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
9
+ //! ```
10
+ //!
11
+ //! Announce response:
12
+ //!
13
+ //! ```json
14
+ //! {
15
+ //! "transaction_id": -888840697
16
+ //! "announce_interval": 120,
17
+ //! "leechers": 0,
18
+ //! "seeders": 1,
19
+ //! "peers": [
20
+ //! "123.123.123.123:51289"
21
+ //! ],
22
+ //! }
23
+ /// ````
2
24
use std:: net:: { Ipv4Addr , SocketAddr } ;
3
25
use std:: str:: FromStr ;
4
26
27
+ use anyhow:: Context ;
5
28
use aquatic_udp_protocol:: common:: InfoHash ;
29
+ use aquatic_udp_protocol:: Response :: { AnnounceIpv4 , AnnounceIpv6 } ;
6
30
use aquatic_udp_protocol:: {
7
31
AnnounceEvent , AnnounceRequest , ConnectRequest , ConnectionId , NumberOfBytes , NumberOfPeers , PeerId , PeerKey , Port , Response ,
8
32
TransactionId ,
9
33
} ;
34
+ use clap:: { Parser , Subcommand } ;
10
35
use log:: { debug, LevelFilter } ;
36
+ use serde_json:: json;
11
37
use torrust_tracker:: shared:: bit_torrent:: info_hash:: InfoHash as TorrustInfoHash ;
12
38
use torrust_tracker:: shared:: bit_torrent:: tracker:: udp:: client:: { UdpClient , UdpTrackerClient } ;
13
39
14
40
const ASSIGNED_BY_OS : i32 = 0 ;
15
41
const RANDOM_TRANSACTION_ID : i32 = -888_840_697 ;
16
42
43
+ #[ derive( Parser , Debug ) ]
44
+ #[ command( author, version, about, long_about = None ) ]
45
+ struct Args {
46
+ #[ command( subcommand) ]
47
+ command : Command ,
48
+ }
49
+
50
+ #[ derive( Subcommand , Debug ) ]
51
+ enum Command {
52
+ Announce {
53
+ #[ arg( value_parser = parse_socket_addr) ]
54
+ tracker_socket_addr : SocketAddr ,
55
+ #[ arg( value_parser = parse_info_hash) ]
56
+ info_hash : TorrustInfoHash ,
57
+ } ,
58
+ }
59
+
17
60
#[ tokio:: main]
18
- async fn main ( ) {
61
+ async fn main ( ) -> anyhow :: Result < ( ) > {
19
62
setup_logging ( LevelFilter :: Info ) ;
20
63
21
- let ( remote_socket_addr , info_hash ) = parse_arguments ( ) ;
64
+ let args = Args :: parse ( ) ;
22
65
23
66
// Configuration
24
67
let local_port = ASSIGNED_BY_OS ;
25
68
let transaction_id = RANDOM_TRANSACTION_ID ;
26
69
let bind_to = format ! ( "0.0.0.0:{local_port}" ) ;
27
70
28
71
// Bind to local port
29
-
30
72
debug ! ( "Binding to: {bind_to}" ) ;
31
73
let udp_client = UdpClient :: bind ( & bind_to) . await ;
32
74
let bound_to = udp_client. socket . local_addr ( ) . unwrap ( ) ;
33
75
debug ! ( "Bound to: {bound_to}" ) ;
34
76
35
- // Connect to remote socket
77
+ let response = match args. command {
78
+ Command :: Announce {
79
+ tracker_socket_addr,
80
+ info_hash,
81
+ } => {
82
+ debug ! ( "Connecting to remote: udp://{tracker_socket_addr}" ) ;
36
83
37
- debug ! ( "Connecting to remote: udp://{remote_socket_addr}" ) ;
38
- udp_client. connect ( & remote_socket_addr) . await ;
84
+ udp_client. connect ( & tracker_socket_addr. to_string ( ) ) . await ;
39
85
40
- let udp_tracker_client = UdpTrackerClient { udp_client } ;
86
+ let udp_tracker_client = UdpTrackerClient { udp_client } ;
41
87
42
- let transaction_id = TransactionId ( transaction_id) ;
88
+ let transaction_id = TransactionId ( transaction_id) ;
43
89
44
- let connection_id = send_connection_request ( transaction_id, & udp_tracker_client) . await ;
90
+ let connection_id = send_connection_request ( transaction_id, & udp_tracker_client) . await ;
45
91
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 ;
92
+ send_announce_request (
93
+ connection_id,
94
+ transaction_id,
95
+ info_hash,
96
+ Port ( bound_to. port ( ) ) ,
97
+ & udp_tracker_client,
98
+ )
99
+ . await
100
+ }
101
+ } ;
102
+
103
+ match response {
104
+ AnnounceIpv4 ( announce) => {
105
+ let json = json ! ( {
106
+ "transaction_id" : announce. transaction_id. 0 ,
107
+ "announce_interval" : announce. announce_interval. 0 ,
108
+ "leechers" : announce. leechers. 0 ,
109
+ "seeders" : announce. seeders. 0 ,
110
+ "peers" : announce. peers. iter( ) . map( |peer| format!( "{}:{}" , peer. ip_address, peer. port. 0 ) ) . collect:: <Vec <_>>( ) ,
111
+ } ) ;
112
+ let pretty_json = serde_json:: to_string_pretty ( & json) . unwrap ( ) ;
113
+ println ! ( "{pretty_json}" ) ;
114
+ }
115
+ AnnounceIpv6 ( announce) => {
116
+ let json = json ! ( {
117
+ "transaction_id" : announce. transaction_id. 0 ,
118
+ "announce_interval" : announce. announce_interval. 0 ,
119
+ "leechers" : announce. leechers. 0 ,
120
+ "seeders" : announce. seeders. 0 ,
121
+ "peers6" : announce. peers. iter( ) . map( |peer| format!( "{}:{}" , peer. ip_address, peer. port. 0 ) ) . collect:: <Vec <_>>( ) ,
122
+ } ) ;
123
+ let pretty_json = serde_json:: to_string_pretty ( & json) . unwrap ( ) ;
124
+ println ! ( "{pretty_json}" ) ;
125
+ }
126
+ _ => println ! ( "{response:#?}" ) ,
127
+ }
54
128
55
- println ! ( "{response:#?}" ) ;
129
+ Ok ( ( ) )
56
130
}
57
131
58
132
fn setup_logging ( level : LevelFilter ) {
@@ -76,31 +150,12 @@ fn setup_logging(level: LevelFilter) {
76
150
debug ! ( "logging initialized." ) ;
77
151
}
78
152
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
- }
153
+ fn parse_socket_addr ( s : & str ) -> anyhow:: Result < SocketAddr > {
154
+ s. parse ( ) . with_context ( || format ! ( "failed to parse socket address: `{s}`" ) )
155
+ }
88
156
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)
157
+ fn parse_info_hash ( s : & str ) -> anyhow:: Result < TorrustInfoHash > {
158
+ TorrustInfoHash :: from_str ( s) . map_err ( |e| anyhow:: Error :: msg ( format ! ( "failed to parse info-hash `{s}`: {e:?}" ) ) )
104
159
}
105
160
106
161
async fn send_connection_request ( transaction_id : TransactionId , client : & UdpTrackerClient ) -> ConnectionId {
0 commit comments