2
2
//! round-tripper that works with the bitcoind RPC server. This can be used
3
3
//! if minimal dependencies are a goal and synchronous communication is ok.
4
4
5
+ #[ cfg( feature = "proxy" ) ]
6
+ use socks:: Socks5Stream ;
5
7
use std:: io:: { BufRead , BufReader , Write } ;
8
+ #[ cfg( not( feature = "proxy" ) ) ]
6
9
use std:: net:: TcpStream ;
7
10
use std:: net:: { SocketAddr , ToSocketAddrs } ;
8
11
use std:: time:: { Duration , Instant } ;
@@ -19,6 +22,9 @@ use crate::{Request, Response};
19
22
/// Set to 8332, the default RPC port for bitcoind.
20
23
pub const DEFAULT_PORT : u16 = 8332 ;
21
24
25
+ /// The Default SOCKS5 Port to use for proxy connection.
26
+ pub const DEFAULT_PROXY_PORT : u16 = 9050 ;
27
+
22
28
/// Simple HTTP transport that implements the necessary subset of HTTP for
23
29
/// running a bitcoind RPC client.
24
30
#[ derive( Clone , Debug ) ]
@@ -28,6 +34,10 @@ pub struct SimpleHttpTransport {
28
34
timeout : Duration ,
29
35
/// The value of the `Authorization` HTTP header.
30
36
basic_auth : Option < String > ,
37
+ #[ cfg( feature = "proxy" ) ]
38
+ proxy_addr : net:: SocketAddr ,
39
+ #[ cfg( feature = "proxy" ) ]
40
+ proxy_auth : Option < ( String , String ) > ,
31
41
}
32
42
33
43
impl Default for SimpleHttpTransport {
@@ -40,6 +50,13 @@ impl Default for SimpleHttpTransport {
40
50
path : "/" . to_owned ( ) ,
41
51
timeout : Duration :: from_secs ( 15 ) ,
42
52
basic_auth : None ,
53
+ #[ cfg( feature = "proxy" ) ]
54
+ proxy_addr : net:: SocketAddr :: new (
55
+ net:: IpAddr :: V4 ( net:: Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) ,
56
+ DEFAULT_PROXY_PORT ,
57
+ ) ,
58
+ #[ cfg( feature = "proxy" ) ]
59
+ proxy_auth : None ,
43
60
}
44
61
}
45
62
}
@@ -61,6 +78,20 @@ impl SimpleHttpTransport {
61
78
{
62
79
// Open connection
63
80
let request_deadline = Instant :: now ( ) + self . timeout ;
81
+ #[ cfg( feature = "proxy" ) ]
82
+ let mut sock = if let Some ( ( username, password) ) = & self . proxy_auth {
83
+ Socks5Stream :: connect_with_password (
84
+ self . proxy_addr ,
85
+ self . addr ,
86
+ username. as_str ( ) ,
87
+ password. as_str ( ) ,
88
+ ) ?
89
+ . into_inner ( )
90
+ } else {
91
+ Socks5Stream :: connect ( self . proxy_addr , self . addr ) ?. into_inner ( )
92
+ } ;
93
+
94
+ #[ cfg( not( feature = "proxy" ) ) ]
64
95
let mut sock = TcpStream :: connect_timeout ( & self . addr , self . timeout ) ?;
65
96
66
97
sock. set_read_timeout ( Some ( self . timeout ) ) ?;
@@ -365,6 +396,22 @@ impl Builder {
365
396
self
366
397
}
367
398
399
+ #[ cfg( feature = "proxy" ) ]
400
+ /// Add proxy address to the transport for SOCKS5 proxy
401
+ pub fn proxy_addr < S : AsRef < str > > ( mut self , proxy_addr : S ) -> Result < Self , Error > {
402
+ // We don't expect path in proxy address.
403
+ self . tp . proxy_addr = check_url ( proxy_addr. as_ref ( ) ) ?. 0 ;
404
+ Ok ( self )
405
+ }
406
+
407
+ #[ cfg( feature = "proxy" ) ]
408
+ /// Add optional proxy authentication as ('username', 'password')
409
+ pub fn proxy_auth < S : AsRef < str > > ( mut self , user : S , pass : S ) -> Self {
410
+ self . tp . proxy_auth =
411
+ Some ( ( user, pass) ) . map ( |( u, p) | ( u. as_ref ( ) . to_string ( ) , p. as_ref ( ) . to_string ( ) ) ) ;
412
+ self
413
+ }
414
+
368
415
/// Builds the final `SimpleHttpTransport`
369
416
pub fn build ( self ) -> SimpleHttpTransport {
370
417
self . tp
@@ -390,11 +437,34 @@ impl crate::Client {
390
437
}
391
438
Ok ( crate :: Client :: with_transport ( builder. build ( ) ) )
392
439
}
440
+
441
+ #[ cfg( feature = "proxy" ) ]
442
+ /// Create a new JSON_RPC client using a HTTP-Socks5 proxy transport.
443
+ pub fn http_proxy (
444
+ url : & str ,
445
+ user : Option < String > ,
446
+ pass : Option < String > ,
447
+ proxy_addr : & str ,
448
+ proxy_auth : Option < ( & str , & str ) > ,
449
+ ) -> Result < crate :: Client , Error > {
450
+ let mut builder = Builder :: new ( ) . url ( url) ?;
451
+ if let Some ( user) = user {
452
+ builder = builder. auth ( user, pass) ;
453
+ }
454
+ builder = builder. proxy_addr ( proxy_addr) ?;
455
+ if let Some ( ( user, pass) ) = proxy_auth {
456
+ builder = builder. proxy_auth ( user, pass) ;
457
+ }
458
+ let tp = builder. build ( ) ;
459
+ Ok ( crate :: Client :: with_transport ( tp) )
460
+ }
393
461
}
394
462
395
463
#[ cfg( test) ]
396
464
mod tests {
397
465
use std:: net;
466
+ #[ cfg( feature = "proxy" ) ]
467
+ use std:: str:: FromStr ;
398
468
399
469
use super :: * ;
400
470
use crate :: Client ;
@@ -435,7 +505,14 @@ mod tests {
435
505
"http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]" ,
436
506
] ;
437
507
for u in & valid_urls {
438
- Builder :: new ( ) . url ( * u) . unwrap_or_else ( |_| panic ! ( "error for: {}" , u) ) ;
508
+ let ( addr, path) = check_url ( u) . unwrap ( ) ;
509
+ let builder = Builder :: new ( ) . url ( * u) . unwrap_or_else ( |_| panic ! ( "error for: {}" , u) ) ;
510
+ assert_eq ! ( builder. tp. addr, addr) ;
511
+ assert_eq ! ( builder. tp. path, path) ;
512
+ assert_eq ! ( builder. tp. timeout, Duration :: from_secs( 15 ) ) ;
513
+ assert_eq ! ( builder. tp. basic_auth, None ) ;
514
+ #[ cfg( feature = "proxy" ) ]
515
+ assert_eq ! ( builder. tp. proxy_addr, SocketAddr :: from_str( "127.0.0.1:9050" ) . unwrap( ) ) ;
439
516
}
440
517
441
518
let invalid_urls = [
@@ -465,4 +542,27 @@ mod tests {
465
542
466
543
let _ = Client :: simple_http ( "localhost:22" , None , None ) . unwrap ( ) ;
467
544
}
545
+
546
+ #[ cfg( feature = "proxy" ) ]
547
+ #[ test]
548
+ fn construct_with_proxy ( ) {
549
+ let tp = Builder :: new ( )
550
+ . timeout ( Duration :: from_millis ( 100 ) )
551
+ . url ( "localhost:22" )
552
+ . unwrap ( )
553
+ . auth ( "user" , None )
554
+ . proxy_addr ( "127.0.0.1:9050" )
555
+ . unwrap ( )
556
+ . build ( ) ;
557
+ let _ = Client :: with_transport ( tp) ;
558
+
559
+ let _ = Client :: http_proxy (
560
+ "localhost:22" ,
561
+ None ,
562
+ None ,
563
+ "127.0.0.1:9050" ,
564
+ Some ( ( "user" , "password" ) ) ,
565
+ )
566
+ . unwrap ( ) ;
567
+ }
468
568
}
0 commit comments