v/vlib/net/socks/socks5.v
2024-03-25 12:18:27 +02:00

162 lines
3.1 KiB
V

module socks
import net.ssl
import net
const socks_version5 = u8(5)
const addr_type_ipv4 = u8(1)
const addr_type_fqdn = u8(3)
const addr_type_ipv6 = u8(4)
const no_auth = u8(0)
const auth_user_password = u8(2)
// socks5_dial create new instance of &net.TcpConn
pub fn socks5_dial(proxy_url string, host string, username string, password string) !&net.TcpConn {
mut con := net.dial_tcp(proxy_url)!
return handshake(mut con, host, username, password)!
}
// socks5_ssl_dial create new instance of &ssl.SSLConn
pub fn socks5_ssl_dial(proxy_url string, host string, username string, password string) !&ssl.SSLConn {
mut ssl_conn := ssl.new_ssl_conn(
verify: ''
cert: ''
cert_key: ''
validate: false
in_memory_verification: false
)!
mut con := socks5_dial(proxy_url, host, username, password)!
ssl_conn.connect(mut con, host.all_before_last(':')) or { panic(err) }
return ssl_conn
}
fn handshake(mut con net.TcpConn, host string, username string, password string) !&net.TcpConn {
mut v := [socks.socks_version5, 1]
if username.len > 0 {
v << socks.auth_user_password
} else {
v << socks.no_auth
}
con.write(v)!
mut bf := []u8{len: 2}
con.read(mut bf)!
if bf[0] != socks.socks_version5 {
con.close()!
return error('unexpected protocol version ${bf[0]}')
}
if username.len == 0 {
if bf[1] != 0 {
con.close()!
return error(reply(bf[1]))
}
}
if username.len > 0 {
v.clear()
v << u8(1)
v << u8(username.len)
v << username.bytes()
v << u8(password.len)
v << password.bytes()
con.write(v)!
mut resp := []u8{len: 2}
con.read(mut resp)!
if resp[0] != 1 {
con.close()!
return error('server does not support user/password version 1')
} else if resp[1] != 0 {
con.close()!
return error('user/password login failed')
}
}
v.clear()
v = [socks.socks_version5, 1, 0]
mut port := host.all_after_last(':').u64()
if port == 0 {
port = u64(80)
}
address := host.all_before_last(':')
if address.contains_only('.1234567890') { // ipv4
v << socks.addr_type_ipv4
v << parse_ipv4(address)!
} else if address.contains_only(':1234567890abcdf') {
// v << addr_type_ipv6
// v << parse_ipv4(address)!
// TODO: support ipv6
} else { // domain
if address.len > 255 {
return error('${address} is too long')
} else {
v << socks.addr_type_fqdn
v << u8(address.len)
v << address.bytes()
}
}
v << u8(port >> 8)
v << u8(port)
con.write(v)!
mut bff := []u8{len: v.len}
con.read(mut bff)!
if bff[1] != 0 {
con.close()!
return error(reply(bff[1]))
}
return con
}
fn reply(code u8) string {
match code {
0 {
return 'succeeded'
}
1 {
return 'general SOCKS server failure'
}
2 {
return 'connection not allowed by ruleset'
}
3 {
return 'network unreachable'
}
4 {
return 'host unreachable'
}
5 {
return 'connection refused'
}
6 {
return 'TTL expired'
}
7 {
return 'command not supported'
}
8 {
return 'address type not supported'
}
else {
return 'unknown code: ${code}'
}
}
}
fn parse_ipv4(addr string) ![]u8 {
mut ip := []u8{}
for part in addr.split('.') {
ip << part.u8()
}
return ip
}