io_proxy/socks/v5/
address.rs1use alloc::{
7 string::{String, ToString},
8 vec::Vec,
9};
10use core::net::IpAddr;
11
12use thiserror::Error;
13
14use crate::socks::v5::{ATYP_DOMAIN, ATYP_IPV4, ATYP_IPV6};
15
16#[derive(Clone, Debug, Error, PartialEq, Eq)]
18pub enum Socks5AddressError {
19 #[error("SOCKS5 domain name too long: {0} bytes (max 255)")]
21 DomainTooLong(usize),
22}
23
24#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum Socks5Address {
31 Ipv4(core::net::Ipv4Addr, u16),
33 Ipv6(core::net::Ipv6Addr, u16),
35 Domain(String, u16),
37}
38
39impl Socks5Address {
40 pub fn new(host: &str, port: u16) -> Result<Socks5Address, Socks5AddressError> {
50 match host.parse::<IpAddr>() {
51 Ok(IpAddr::V4(ip)) => Ok(Socks5Address::Ipv4(ip, port)),
52 Ok(IpAddr::V6(ip)) => Ok(Socks5Address::Ipv6(ip, port)),
53 Err(_) => {
54 if host.len() > 255 {
55 return Err(Socks5AddressError::DomainTooLong(host.len()));
56 }
57 Ok(Socks5Address::Domain(host.to_string(), port))
58 }
59 }
60 }
61
62 pub(crate) fn encode_into(&self, out: &mut Vec<u8>) {
64 match self {
65 Socks5Address::Ipv4(ip, port) => {
66 out.push(ATYP_IPV4);
67 out.extend_from_slice(&ip.octets());
68 out.extend_from_slice(&port.to_be_bytes());
69 }
70 Socks5Address::Ipv6(ip, port) => {
71 out.push(ATYP_IPV6);
72 out.extend_from_slice(&ip.octets());
73 out.extend_from_slice(&port.to_be_bytes());
74 }
75 Socks5Address::Domain(name, port) => {
76 out.push(ATYP_DOMAIN);
77 out.push(name.len() as u8);
78 out.extend_from_slice(name.as_bytes());
79 out.extend_from_slice(&port.to_be_bytes());
80 }
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use alloc::vec::Vec;
88
89 use super::*;
90
91 fn encode(addr: &Socks5Address) -> Vec<u8> {
92 let mut out = Vec::new();
93 addr.encode_into(&mut out);
94 out
95 }
96
97 #[test]
98 fn new_classifies_host() {
99 assert!(matches!(
100 Socks5Address::new("1.2.3.4", 993),
101 Ok(Socks5Address::Ipv4(_, 993))
102 ));
103 assert!(matches!(
104 Socks5Address::new("::1", 25),
105 Ok(Socks5Address::Ipv6(_, 25))
106 ));
107 assert!(matches!(
108 Socks5Address::new("imap.example.com", 993),
109 Ok(Socks5Address::Domain(_, 993))
110 ));
111 }
112
113 #[test]
114 fn new_rejects_overlong_domain() {
115 let host = "a".repeat(256);
116 assert_eq!(
117 Socks5Address::new(&host, 1),
118 Err(Socks5AddressError::DomainTooLong(256))
119 );
120 }
121
122 #[test]
123 fn encode_ipv4() {
124 assert_eq!(
126 encode(&Socks5Address::Ipv4([1, 2, 3, 4].into(), 993)),
127 [0x01, 1, 2, 3, 4, 0x03, 0xE1]
128 );
129 }
130
131 #[test]
132 fn encode_domain() {
133 assert_eq!(
135 encode(&Socks5Address::Domain("abc".into(), 80)),
136 [0x03, 0x03, b'a', b'b', b'c', 0x00, 0x50]
137 );
138 }
139
140 #[test]
141 fn encode_ipv6() {
142 let addr = Socks5Address::Ipv6(core::net::Ipv6Addr::LOCALHOST, 443);
143 let mut expected = vec![0x04u8];
144 expected.extend_from_slice(&core::net::Ipv6Addr::LOCALHOST.octets());
145 expected.extend_from_slice(&443u16.to_be_bytes());
146 assert_eq!(encode(&addr), expected);
147 }
148}