Skip to main content

electrum_client/socks/
v4.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4use std::io::{self, Read, Write};
5use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpStream, ToSocketAddrs};
6
7use super::{TargetAddr, ToTargetAddr};
8
9fn read_response(socket: &mut TcpStream) -> io::Result<SocketAddrV4> {
10    let mut response = [0u8; 8];
11    socket.read_exact(&mut response)?;
12    let mut response = &response[..];
13
14    if response.read_u8()? != 0 {
15        return Err(io::Error::new(
16            io::ErrorKind::InvalidData,
17            "invalid response version",
18        ));
19    }
20
21    match response.read_u8()? {
22        90 => {}
23        91 => return Err(io::Error::other("request rejected or failed")),
24        92 => {
25            return Err(io::Error::new(
26                io::ErrorKind::PermissionDenied,
27                "request rejected because SOCKS server cannot connect to \
28                                       idnetd on the client",
29            ))
30        }
31        93 => {
32            return Err(io::Error::new(
33                io::ErrorKind::PermissionDenied,
34                "request rejected because the client program and identd \
35                                       report different user-ids",
36            ))
37        }
38        _ => {
39            return Err(io::Error::new(
40                io::ErrorKind::InvalidData,
41                "invalid response code",
42            ))
43        }
44    }
45
46    let port = response.read_u16::<BigEndian>()?;
47    let ip = Ipv4Addr::from(response.read_u32::<BigEndian>()?);
48
49    Ok(SocketAddrV4::new(ip, port))
50}
51
52/// A SOCKS4 client.
53#[derive(Debug)]
54pub struct Socks4Stream {
55    socket: TcpStream,
56    proxy_addr: SocketAddrV4,
57}
58
59impl Socks4Stream {
60    /// Connects to a target server through a SOCKS4 proxy.
61    ///
62    /// # Note
63    ///
64    /// If `target` is a `TargetAddr::Domain`, the domain name will be forwarded
65    /// to the proxy server using the SOCKS4A protocol extension. If the proxy
66    /// server does not support SOCKS4A, consider performing the DNS lookup
67    /// locally and passing a `TargetAddr::Ip`.
68    pub fn connect<T, U>(proxy: T, target: U, userid: &str) -> io::Result<Socks4Stream>
69    where
70        T: ToSocketAddrs,
71        U: ToTargetAddr,
72    {
73        Self::connect_raw(1, proxy, target, userid)
74    }
75
76    fn connect_raw<T, U>(command: u8, proxy: T, target: U, userid: &str) -> io::Result<Socks4Stream>
77    where
78        T: ToSocketAddrs,
79        U: ToTargetAddr,
80    {
81        let mut socket = TcpStream::connect(proxy)?;
82
83        let target = target.to_target_addr()?;
84
85        let mut packet = vec![];
86        let _ = packet.write_u8(4); // version
87        let _ = packet.write_u8(command); // command code
88        match target.to_target_addr()? {
89            TargetAddr::Ip(addr) => {
90                let addr = match addr {
91                    SocketAddr::V4(addr) => addr,
92                    SocketAddr::V6(_) => {
93                        return Err(io::Error::new(
94                            io::ErrorKind::InvalidInput,
95                            "SOCKS4 does not support IPv6",
96                        ));
97                    }
98                };
99                let _ = packet.write_u16::<BigEndian>(addr.port());
100                let _ = packet.write_u32::<BigEndian>((*addr.ip()).into());
101                let _ = packet.write_all(userid.as_bytes());
102                let _ = packet.write_u8(0);
103            }
104            TargetAddr::Domain(ref host, port) => {
105                let _ = packet.write_u16::<BigEndian>(port);
106                let _ = packet.write_u32::<BigEndian>(Ipv4Addr::new(0, 0, 0, 1).into());
107                let _ = packet.write_all(userid.as_bytes());
108                let _ = packet.write_u8(0);
109                packet.extend(host.as_bytes());
110                let _ = packet.write_u8(0);
111            }
112        }
113
114        socket.write_all(&packet)?;
115        let proxy_addr = read_response(&mut socket)?;
116
117        Ok(Socks4Stream { socket, proxy_addr })
118    }
119
120    /// Returns the proxy-side address of the connection between the proxy and
121    /// target server.
122    pub fn proxy_addr(&self) -> SocketAddrV4 {
123        self.proxy_addr
124    }
125
126    /// Returns a shared reference to the inner `TcpStream`.
127    pub fn get_ref(&self) -> &TcpStream {
128        &self.socket
129    }
130
131    /// Returns a mutable reference to the inner `TcpStream`.
132    pub fn get_mut(&mut self) -> &mut TcpStream {
133        &mut self.socket
134    }
135
136    /// Consumes the `Socks4Stream`, returning the inner `TcpStream`.
137    pub fn into_inner(self) -> TcpStream {
138        self.socket
139    }
140}
141
142impl Read for Socks4Stream {
143    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
144        self.socket.read(buf)
145    }
146}
147
148impl Read for &Socks4Stream {
149    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
150        (&self.socket).read(buf)
151    }
152}
153
154impl Write for Socks4Stream {
155    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
156        self.socket.write(buf)
157    }
158
159    fn flush(&mut self) -> io::Result<()> {
160        self.socket.flush()
161    }
162}
163
164impl Write for &Socks4Stream {
165    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
166        (&self.socket).write(buf)
167    }
168
169    fn flush(&mut self) -> io::Result<()> {
170        (&self.socket).flush()
171    }
172}
173
174/// A SOCKS4 BIND client.
175#[derive(Debug)]
176pub struct Socks4Listener(Socks4Stream);
177
178impl Socks4Listener {
179    /// Initiates a BIND request to the specified proxy.
180    ///
181    /// The proxy will filter incoming connections based on the value of
182    /// `target`.
183    pub fn bind<T, U>(proxy: T, target: U, userid: &str) -> io::Result<Socks4Listener>
184    where
185        T: ToSocketAddrs,
186        U: ToTargetAddr,
187    {
188        Socks4Stream::connect_raw(2, proxy, target, userid).map(Socks4Listener)
189    }
190
191    /// The address of the proxy-side TCP listener.
192    ///
193    /// This should be forwarded to the remote process, which should open a
194    /// connection to it.
195    pub fn proxy_addr(&self) -> io::Result<SocketAddr> {
196        if self.0.proxy_addr.ip().octets() != [0, 0, 0, 0] {
197            Ok(SocketAddr::V4(self.0.proxy_addr()))
198        } else {
199            let port = self.0.proxy_addr.port();
200            let peer = match self.0.socket.peer_addr()? {
201                SocketAddr::V4(addr) => SocketAddr::V4(SocketAddrV4::new(*addr.ip(), port)),
202                SocketAddr::V6(addr) => SocketAddr::V6(SocketAddrV6::new(*addr.ip(), port, 0, 0)),
203            };
204            Ok(peer)
205        }
206    }
207
208    /// Waits for the remote process to connect to the proxy server.
209    ///
210    /// The value of `proxy_addr` should be forwarded to the remote process
211    /// before this method is called.
212    pub fn accept(mut self) -> io::Result<Socks4Stream> {
213        self.0.proxy_addr = read_response(&mut self.0.socket)?;
214        Ok(self.0)
215    }
216}
217
218#[cfg(test)]
219mod test {
220    use std::io::{Read, Write};
221    use std::net::{SocketAddr, SocketAddrV4, TcpStream, ToSocketAddrs};
222
223    use super::*;
224
225    fn google_ip() -> SocketAddrV4 {
226        "google.com:80"
227            .to_socket_addrs()
228            .unwrap()
229            .filter_map(|a| match a {
230                SocketAddr::V4(a) => Some(a),
231                SocketAddr::V6(_) => None,
232            })
233            .next()
234            .unwrap()
235    }
236
237    #[test]
238    #[ignore]
239    fn google() {
240        let mut socket = Socks4Stream::connect("127.0.0.1:1080", google_ip(), "").unwrap();
241
242        socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
243        let mut result = vec![];
244        socket.read_to_end(&mut result).unwrap();
245
246        println!("{}", String::from_utf8_lossy(&result));
247        assert!(result.starts_with(b"HTTP/1.0"));
248        assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
249    }
250
251    #[test]
252    #[ignore] // dante doesn't support SOCKS4A
253    fn google_dns() {
254        let mut socket = Socks4Stream::connect("127.0.0.1:8080", "google.com:80", "").unwrap();
255
256        socket.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
257        let mut result = vec![];
258        socket.read_to_end(&mut result).unwrap();
259
260        println!("{}", String::from_utf8_lossy(&result));
261        assert!(result.starts_with(b"HTTP/1.0"));
262        assert!(result.ends_with(b"</HTML>\r\n") || result.ends_with(b"</html>"));
263    }
264
265    #[test]
266    #[ignore]
267    fn bind() {
268        // First figure out our local address that we'll be connecting from
269        let socket = Socks4Stream::connect("127.0.0.1:1080", google_ip(), "").unwrap();
270        let addr = socket.proxy_addr();
271
272        let listener = Socks4Listener::bind("127.0.0.1:1080", addr, "").unwrap();
273        let addr = listener.proxy_addr().unwrap();
274        let mut end = TcpStream::connect(addr).unwrap();
275        let mut conn = listener.accept().unwrap();
276        conn.write_all(b"hello world").unwrap();
277        drop(conn);
278        let mut result = vec![];
279        end.read_to_end(&mut result).unwrap();
280        assert_eq!(result, b"hello world");
281    }
282}