1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2025-07-07 08:14:58 +03:00

Add support for SOCKS5 proxy protocol

This commit is contained in:
Davide Beatrici
2018-10-30 15:11:16 +01:00
parent 919cb2ae75
commit 53e0a2e5e2
16 changed files with 791 additions and 147 deletions

View File

@ -6271,8 +6271,7 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
{
SOCK *s = NULL;
CLIENT_OPTION *o;
char *host_for_direct_connection;
UINT port_for_direct_connection;
WPC_CONNECT w;
wchar_t tmp[MAX_SIZE];
SESSION *sess;
volatile bool *cancel_flag = NULL;
@ -6288,6 +6287,7 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
}
Zero(&ret_ip, sizeof(IP));
Zero(&w, sizeof(w));
sess = c->Session;
@ -6323,21 +6323,26 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
c->ServerPort = o->Port;
}
host_for_direct_connection = c->ServerName;
port_for_direct_connection = c->ServerPort;
StrCpy(w.HostName, sizeof(w.HostName), c->ServerName);
w.Port = c->ServerPort;
StrCpy(w.ProxyHostName, sizeof(w.ProxyHostName), o->ProxyName);
w.ProxyPort = o->ProxyPort;
StrCpy(w.ProxyUsername, sizeof(w.ProxyUsername), o->ProxyUsername);
StrCpy(w.ProxyPassword, sizeof(w.ProxyPassword), o->ProxyPassword);
switch (o->ProxyType)
{
case PROXY_DIRECT: // TCP/IP
UniFormat(tmp, sizeof(tmp), _UU("STATUS_4"), c->ServerName);
UniFormat(tmp, sizeof(tmp), _UU("STATUS_4"), w.HostName);
PrintStatus(sess, tmp);
// Production job
if (o->PortUDP == 0)
{
{
// If additional_connect == false, enable trying to NAT-T connection
// If additional_connect == true, follow the IsRUDPSession setting in this session
s = TcpIpConnectEx(host_for_direct_connection, port_for_direct_connection,
s = TcpIpConnectEx(w.HostName, w.Port,
(bool *)cancel_flag, hWnd, &nat_t_err, (additional_connect ? (!is_additional_rudp_session) : false),
true, &ret_ip);
}
@ -6376,16 +6381,12 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
break;
case PROXY_HTTP: // HTTP Proxy
host_for_direct_connection = o->ProxyName;
port_for_direct_connection = o->ProxyPort;
UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), c->ServerName, o->ProxyName);
UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName);
PrintStatus(sess, tmp);
// Proxy connection
s = ProxyConnectEx(c, host_for_direct_connection, port_for_direct_connection,
c->ServerName, c->ServerPort, o->ProxyUsername, o->ProxyPassword,
s = ProxyConnectEx(c, w.ProxyHostName, w.ProxyPort,
w.HostName, w.Port, w.ProxyUsername, w.ProxyPassword,
additional_connect, (bool *)cancel_flag, hWnd);
if (s == NULL)
{
@ -6394,19 +6395,27 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
}
break;
case PROXY_SOCKS: // SOCKS Proxy
host_for_direct_connection = o->ProxyName;
port_for_direct_connection = o->ProxyPort;
UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), c->ServerName, o->ProxyName);
case PROXY_SOCKS: // SOCKS4 Proxy
UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName);
PrintStatus(sess, tmp);
// SOCKS4 connection
s = SocksConnectEx2(c, w.ProxyHostName, w.ProxyPort,
w.HostName, w.Port, w.ProxyUsername, additional_connect, (bool *)cancel_flag,
hWnd, 0, &ret_ip);
if (s == NULL)
{
// Connection failure
return NULL;
}
break;
// SOCKS connection
s = SocksConnectEx2(c, host_for_direct_connection, port_for_direct_connection,
c->ServerName, c->ServerPort, o->ProxyUsername,
additional_connect, (bool *)cancel_flag, hWnd, 0, &ret_ip);
case PROXY_SOCKS5: // SOCKS5 Proxy
UniFormat(tmp, sizeof(tmp), _UU("STATUS_2"), w.HostName, w.ProxyHostName);
PrintStatus(sess, tmp);
// SOCKS5 connection
s = Socks5Connect(c, &w, additional_connect, (bool *)cancel_flag, hWnd, 0, &ret_ip);
if (s == NULL)
{
// Connection failure
@ -6426,7 +6435,8 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
// Keep a note of the IP address
if (additional_connect == false || IsZeroIP(&s->RemoteIP))
{
if (((s->IsRUDPSocket || s->IPv6) && IsZeroIP(&s->RemoteIP) == false && o->ProxyType == PROXY_DIRECT) || GetIP(&c->Session->ServerIP, host_for_direct_connection) == false)
char *hostname = o->ProxyType == PROXY_DIRECT ? w.HostName : w.ProxyHostName;
if (((s->IsRUDPSocket || s->IPv6) && IsZeroIP(&s->RemoteIP) == false && o->ProxyType == PROXY_DIRECT) || GetIP(&c->Session->ServerIP, hostname) == false)
{
Copy(&c->Session->ServerIP, &s->RemoteIP, sizeof(IP));
}
@ -6449,7 +6459,7 @@ SOCK *ClientConnectGetSocket(CONNECTION *c, bool additional_connect)
return s;
}
// Connect via SOCKS
// Connect via SOCKS4
SOCK *SocksConnect(CONNECTION *c, char *proxy_host_name, UINT proxy_port,
char *server_host_name, UINT server_port,
char *username, bool additional_connect)
@ -6547,7 +6557,7 @@ SOCK *SocksConnectEx2(CONNECTION *c, char *proxy_host_name, UINT proxy_port,
return s;
}
// Receive a SOCKS response packet
// Receive a SOCKS4 response packet
bool SocksRecvResponsePacket(CONNECTION *c, SOCK *s)
{
BUF *b;
@ -6599,7 +6609,7 @@ bool SocksRecvResponsePacket(CONNECTION *c, SOCK *s)
}
}
// Send a SOCKS request packet
// Send a SOCKS4 request packet
bool SocksSendRequestPacket(CONNECTION *c, SOCK *s, UINT dest_port, IP *dest_ip, char *userid)
{
BUF *b;
@ -6639,6 +6649,321 @@ bool SocksSendRequestPacket(CONNECTION *c, SOCK *s, UINT dest_port, IP *dest_ip,
return ret;
}
// Connect via SOCKS5 (RFC1928)
SOCK *Socks5Connect(CONNECTION *c, WPC_CONNECT *w, bool additional_connect, bool *cancel_flag, void *hWnd, UINT timeout, IP *ret_ip)
{
UCHAR tmp, recv_buf[2], *recv_buf_final;
USHORT port;
bool ret;
SOCK *s;
BUF *b;
IP ip;
// Validate arguments
if (c == NULL || w == NULL || w->Port == 0 || w->ProxyPort == 0 || IsEmptyStr(w->HostName) || IsEmptyStr(w->ProxyHostName))
{
if (c != NULL)
{
c->Err = ERR_PROXY_CONNECT_FAILED;
}
return NULL;
}
if (c->Halt)
{
// Stop
c->Err = ERR_USER_CANCEL;
return NULL;
}
// Open TCP connection to the proxy server
s = TcpConnectEx3(w->ProxyHostName, w->ProxyPort, timeout, cancel_flag, hWnd, true, NULL, false, ret_ip);
if (s == NULL)
{
// Failure
c->Err = ERR_PROXY_CONNECT_FAILED;
return NULL;
}
// Set the timeout setting
SetTimeout(s, MIN(CONNECTING_TIMEOUT_PROXY, (timeout == 0 ? INFINITE : timeout)));
if (additional_connect == false)
{
c->FirstSock = s;
}
// +----+----------+----------+
// |VER | NMETHODS | METHODS |
// +----+----------+----------+
// | 1 | 1 | 1 to 255 |
// +----+----------+----------+
//
// X'00' NO AUTHENTICATION REQUIRED
// X'01' GSSAPI
// X'02' USERNAME/PASSWORD
// X'03' to X'7F' IANA ASSIGNED
// X'80' to X'FE' RESERVED FOR PRIVATE METHODS
// X'FF' NO ACCEPTABLE METHOD
b = NewBuf();
tmp = 5;
WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version
tmp = 2;
WriteBuf(b, &tmp, sizeof(tmp)); // Number of supported methods
tmp = 0;
WriteBuf(b, &tmp, sizeof(tmp)); // No authentication
tmp = 2;
WriteBuf(b, &tmp, sizeof(tmp)); // Username/password
ret = SendAll(s, b->Buf, b->Size, false);
FreeBuf(b);
if (ret == false)
{
Debug("Socks5Connect(): [Phase 1] Failed to send initial data to the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
// +----+--------+
// |VER | METHOD |
// +----+--------+
// | 1 | 1 |
// +----+--------+
if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false)
{
Debug("Socks5Connect(): [Phase 1] Failed to receive initial data response from the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
if (recv_buf[0] != 5)
{
Debug("Socks5Connect(): [Phase 1] Unmatching version: %u.\n", recv_buf[0]);
c->Err = ERR_PROXY_ERROR;
goto failure;
}
// Username/password authentication (RFC1929)
if (recv_buf[1] == 2)
{
// +----+------+----------+------+----------+
// |VER | ULEN | UNAME | PLEN | PASSWD |
// +----+------+----------+------+----------+
// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
// +----+------+----------+------+----------+
b = NewBuf();
tmp = 1;
WriteBuf(b, &tmp, sizeof(tmp)); // Authentication protocol version
tmp = StrLen(w->ProxyUsername);
WriteBuf(b, &tmp, sizeof(tmp)); // Username length
WriteBuf(b, w->ProxyUsername, tmp); // Username
tmp = StrLen(w->ProxyPassword);
WriteBuf(b, &tmp, sizeof(tmp)); // Password length
WriteBuf(b, w->ProxyPassword, tmp); // Password
ret = SendAll(s, b->Buf, b->Size, false);
FreeBuf(b);
if (ret == false)
{
Debug("Socks5Connect(): [Phase 1] Failed to send authentication data to the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
// +----+--------+
// |VER | STATUS |
// +----+--------+
// | 1 | 1 |
// +----+--------+
if (RecvAll(s, recv_buf, sizeof(recv_buf), false) == false)
{
Debug("Socks5Connect(): [Phase 1] Failed to receive authentication data response from the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
if (recv_buf[1] != 0)
{
Debug("Socks5Connect(): [Phase 1] Authentication failure error code sent by the server: %u.\n", recv_buf[1]);
c->Err = ERR_PROXY_AUTH_FAILED;
goto failure;
}
}
// +----+-----+-------+------+----------+----------+
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X'00' | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// VER protocol version: X'05'
// CMD
// CONNECT X'01'
// BIND X'02'
// UDP ASSOCIATE X'03'
// RSV RESERVED
// ATYP address type of following address
// IP V4 address X'01'
// DOMAINNAME X'03'
// IP V6 address X'04'
// DST.ADDR desired destination address
// DST.PORT desired destination port in network octet order
// Prepare data to send
b = NewBuf();
tmp = 5;
WriteBuf(b, &tmp, sizeof(tmp)); // SOCKS version
tmp = 1;
WriteBuf(b, &tmp, sizeof(tmp)); // Command
tmp = 0;
WriteBuf(b, &tmp, sizeof(tmp)); // Reserved byte
// Convert the hostname to an IP structure (if it's an IP address)
StrToIP(&ip, w->HostName);
// If the IP structure doesn't contain an IP address, it means that the string is an hostname
if (IsZeroIp(&ip))
{
UCHAR dest_length = StrLen(w->HostName);
tmp = 3;
WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (hostname)
WriteBuf(b, &dest_length, sizeof(dest_length)); // Destination hostname length
WriteBuf(b, w->HostName, dest_length); // Destination hostname
}
else
{
if (IsIP6(&ip))
{
tmp = 4;
WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv6)
WriteBuf(b, ip.ipv6_addr, sizeof(ip.ipv6_addr)); // Destination IPv6 address
}
else
{
tmp = 1;
WriteBuf(b, &tmp, sizeof(tmp)); // Destination type (IPv4)
WriteBuf(b, ip.addr, sizeof(ip.addr)); // Destination IPv4 address
}
}
// Convert the port in network octet order
port = Endian16((USHORT)w->Port);
WriteBuf(b, &port, sizeof(port)); // Destination port
// Send data
ret = SendAll(s, b->Buf, b->Size, false);
FreeBuf(b);
if (ret == false)
{
Debug("Socks5Connect(): [Phase 2] Failed to send data to the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
// +----+-----+-------+------+----------+----------+
// |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
// +----+-----+-------+------+----------+----------+
// | 1 | 1 | X00 | 1 | Variable | 2 |
// +----+-----+-------+------+----------+----------+
//
// VER protocol version: X05
// REP Reply field:
// X00 succeeded
// X01 general SOCKS server failure
// X02 connection not allowed by ruleset
// X03 Network unreachable
// X04 Host unreachable
// X05 Connection refused
// X06 TTL expired
// X07 Command not supported
// X08 Address type not supported
// X09 to XFF unassigned
// The packet sent by the server should always have the same size as the one we sent to it.
// However, there are some implementations which send fixed values (aside from the first 2 bytes).
// In order to support such implementations, we read the first 4 bytes in order to know the address type before trying to read the rest of the packet.
recv_buf_final = Malloc(4);
if (RecvAll(s, recv_buf_final, 4, false) == false)
{
Free(recv_buf_final);
Debug("Socks5Connect(): [Phase 2] Failed to receive response from the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
// We only need the first two bytes (version and response code), but we have to read the entire packet from the socket
recv_buf[0] = recv_buf_final[0];
recv_buf[1] = recv_buf_final[1];
// We receive the rest of the packet by knowing the size according to the address type
switch (recv_buf_final[3])
{
case 1:
// IPv4
recv_buf_final = ReAlloc(recv_buf_final, 6); // 4 bytes (IPv4) + 2 bytes (port)
ret = RecvAll(s, recv_buf_final, 6, false);
break;
case 4:
// IPv6
recv_buf_final = ReAlloc(recv_buf_final, 18); // 4 bytes (IPv4) + 2 bytes (port)
ret = RecvAll(s, recv_buf_final, 18, false);
break;
case 3:
// Hostname
ret = RecvAll(s, &tmp, 1, false);
if (ret == true)
{
recv_buf_final = ReAlloc(recv_buf_final, tmp + 2); // Hostname length + 2 bytes (port)
ret = RecvAll(s, recv_buf_final, tmp + 2, false);
}
}
Free(recv_buf_final);
if (ret == false)
{
Debug("Socks5Connect(): [Phase 2] Malformed response received from the server.\n");
c->Err = ERR_DISCONNECTED;
goto failure;
}
if (recv_buf[0] != 5)
{
Debug("Socks5Connect(): [Phase 2] Unmatching version: %u.\n", recv_buf_final[0]);
c->Err = ERR_PROXY_ERROR;
goto failure;
}
if (recv_buf[1] == 0)
{
// Success
SetTimeout(s, INFINITE);
return s;
}
else
{
Debug("Socks5Connect(): [Phase 2] Connection failed with error: %u\n", recv_buf[1]);
c->Err = ERR_PROXY_ERROR;
}
failure:
if (additional_connect == false)
{
c->FirstSock = NULL;
}
Disconnect(s);
ReleaseSock(s);
return NULL;
}
// Connect through a proxy
SOCK *ProxyConnect(CONNECTION *c, char *proxy_host_name, UINT proxy_port,
char *server_host_name, UINT server_port,