Browse Source

Refine local server_handshake

* Removed STAGE_PARSE and the 5-year-old hacky "fake" reply;
* SOCKS5 server returns more error code than 0x07;
* Ensure `ip` is initialized in all cases; (I guess security guys don't want to audit the local part)
* Code more readable magically.
pull/2132/head
Mygod 6 years ago
parent
commit
4a97acd44e
3 changed files with 143 additions and 116 deletions
  1. 1
      src/common.h
  2. 233
      src/local.c
  3. 25
      src/socks5.h

1
src/common.h

@ -51,7 +51,6 @@ int send_traffic_stat(uint64_t tx, uint64_t rx);
#define STAGE_ERROR -1 /* Error detected */
#define STAGE_INIT 0 /* Initial stage */
#define STAGE_HANDSHAKE 1 /* Handshake with client */
#define STAGE_PARSE 2 /* Parse the SOCKS5 header */
#define STAGE_SNI 3 /* Parse HTTP/SNI header */
#define STAGE_RESOLVE 4 /* Resolve the hostname */
#define STAGE_STREAM 5 /* Stream between client and server */

233
src/local.c

@ -277,6 +277,58 @@ delayed_connect_cb(EV_P_ ev_timer *watcher, int revents)
server_recv_cb(EV_A_ & server->recv_ctx->io, revents);
}
static int
server_handshake_reply(EV_P_ ev_io *w, int udp_assc, struct socks5_response *response)
{
server_ctx_t *server_recv_ctx = (server_ctx_t *)w;
server_t *server = server_recv_ctx->server;
remote_t *remote = server->remote;
if (server->stage != STAGE_HANDSHAKE) return 0;
struct sockaddr_in sock_addr;
if (udp_assc) {
socklen_t addr_len = sizeof(sock_addr);
if (getsockname(udp_fd, (struct sockaddr *)&sock_addr, &addr_len) < 0) {
LOGE("getsockname: %s", strerror(errno));
response->rep = SOCKS5_REP_CONN_REFUSED;
send(server->fd, (char *)response, sizeof(struct socks5_response), 0);
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return -1;
}
} else memset(&sock_addr, 0, sizeof(sock_addr));
buffer_t resp_to_send;
buffer_t *resp_buf = &resp_to_send;
balloc(resp_buf, BUF_SIZE);
memcpy(resp_buf->data, response, sizeof(struct socks5_response));
memcpy(resp_buf->data + sizeof(struct socks5_response),
&sock_addr.sin_addr, sizeof(sock_addr.sin_addr));
memcpy(resp_buf->data + sizeof(struct socks5_response) +
sizeof(sock_addr.sin_addr),
&sock_addr.sin_port, sizeof(sock_addr.sin_port));
int reply_size = sizeof(struct socks5_response) +
sizeof(sock_addr.sin_addr) + sizeof(sock_addr.sin_port);
int s = send(server->fd, resp_buf->data, reply_size, 0);
bfree(resp_buf);
if (s < reply_size) {
LOGE("failed to send fake reply");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return -1;
}
if (udp_assc) {
// Wait until client closes the connection
return -1;
}
return 0;
}
static int
server_handshake(EV_P_ ev_io *w, buffer_t *buf)
{
@ -286,30 +338,25 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
struct socks5_request *request = (struct socks5_request *)buf->data;
size_t request_len = sizeof(struct socks5_request);
struct sockaddr_in sock_addr;
memset(&sock_addr, 0, sizeof(sock_addr));
if (buf->len < request_len) {
return -1;
}
int udp_assc = 0;
struct socks5_response response;
response.ver = SVERSION;
response.rep = SOCKS5_REP_SUCCEEDED;
response.rsv = 0;
response.atyp = SOCKS5_ATYP_IPV4;
if (request->cmd == 3) {
udp_assc = 1;
socklen_t addr_len = sizeof(sock_addr);
getsockname(udp_fd, (struct sockaddr *)&sock_addr,
&addr_len);
if (request->cmd == SOCKS5_CMD_UDP_ASSOCIATE) {
if (verbose) {
LOGI("udp assc request accepted");
}
} else if (request->cmd != 1) {
return server_handshake_reply(EV_A_ w, 1, &response);
} else if (request->cmd != SOCKS5_CMD_CONNECT) {
LOGE("unsupported cmd: %d", request->cmd);
struct socks5_response response;
response.ver = SVERSION;
response.rep = CMD_NOT_SUPPORTED;
response.rsv = 0;
response.atyp = 1;
response.rep = SOCKS5_REP_CMD_NOT_SUPPORTED;
char *send_buf = (char *)&response;
send(server->fd, send_buf, 4, 0);
close_and_free_remote(EV_A_ remote);
@ -317,46 +364,6 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
return -1;
}
// Fake reply
if (server->stage == STAGE_HANDSHAKE) {
struct socks5_response response;
response.ver = SVERSION;
response.rep = 0;
response.rsv = 0;
response.atyp = 1;
buffer_t resp_to_send;
buffer_t *resp_buf = &resp_to_send;
balloc(resp_buf, BUF_SIZE);
memcpy(resp_buf->data, &response, sizeof(struct socks5_response));
memcpy(resp_buf->data + sizeof(struct socks5_response),
&sock_addr.sin_addr, sizeof(sock_addr.sin_addr));
memcpy(resp_buf->data + sizeof(struct socks5_response) +
sizeof(sock_addr.sin_addr),
&sock_addr.sin_port, sizeof(sock_addr.sin_port));
int reply_size = sizeof(struct socks5_response) +
sizeof(sock_addr.sin_addr) + sizeof(sock_addr.sin_port);
int s = send(server->fd, resp_buf->data, reply_size, 0);
bfree(resp_buf);
if (s < reply_size) {
LOGE("failed to send fake reply");
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return -1;
}
if (udp_assc) {
// Wait until client closes the connection
return -1;
}
server->stage = STAGE_PARSE;
}
char host[257], ip[INET6_ADDRSTRLEN], port[16];
buffer_t *abuf = server->abuf;
@ -367,8 +374,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
int atyp = request->atyp;
// get remote addr and port
if (atyp == 1) {
// IP V4
if (atyp == SOCKS5_ATYP_IPV4) {
size_t in_addr_len = sizeof(struct in_addr);
if (buf->len < request_len + in_addr_len + 2) {
return -1;
@ -378,12 +384,18 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
if (acl || verbose) {
uint16_t p = ntohs(*(uint16_t *)(buf->data + request_len + in_addr_len));
inet_ntop(AF_INET, (const void *)(buf->data + request_len),
ip, INET_ADDRSTRLEN);
if (!inet_ntop(AF_INET, (const void *)(buf->data + request_len),
ip, INET_ADDRSTRLEN)) {
LOGI("inet_ntop(AF_INET): %s", strerror(errno));
ip[0] = '\0';
}
sprintf(port, "%d", p);
}
} else if (atyp == 3) {
// Domain name
} else if (atyp == SOCKS5_ATYP_DOMAIN
#ifdef __ANDROID__
&& !vpn // protecting DNS packets isn't supported yet
#endif
) {
uint8_t name_len = *(uint8_t *)(buf->data + request_len);
if (buf->len < request_len + 1 + name_len + 2) {
return -1;
@ -399,8 +411,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
host[name_len] = '\0';
sprintf(port, "%d", p);
}
} else if (atyp == 4) {
// IP V6
} else if (atyp == SOCKS5_ATYP_IPV6) {
size_t in6_addr_len = sizeof(struct in6_addr);
if (buf->len < request_len + in6_addr_len + 2) {
return -1;
@ -410,12 +421,18 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
if (acl || verbose) {
uint16_t p = ntohs(*(uint16_t *)(buf->data + request_len + in6_addr_len));
inet_ntop(AF_INET6, (const void *)(buf->data + request_len),
ip, INET6_ADDRSTRLEN);
if (!inet_ntop(AF_INET6, (const void *)(buf->data + request_len),
ip, INET6_ADDRSTRLEN)) {
LOGI("inet_ntop(AF_INET6): %s", strerror(errno));
ip[0] = '\0';
}
sprintf(port, "%d", p);
}
} else {
LOGE("unsupported addrtype: %d", request->atyp);
response.rep = SOCKS5_REP_ADDRTYPE_NOT_SUPPORTED;
char *send_buf = (char *)&response;
send(server->fd, send_buf, 4, 0);
close_and_free_remote(EV_A_ remote);
close_and_free_server(EV_A_ server);
return -1;
@ -428,7 +445,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
char *hostname;
uint16_t dst_port = ntohs(*(uint16_t *)(abuf->data + abuf->len - 2));
if (atyp == 1 || atyp == 4) {
if (atyp == SOCKS5_ATYP_IPV4 || atyp == SOCKS5_ATYP_IPV6) {
if (dst_port == http_protocol->default_port)
ret = http_protocol->parse_packet(buf->data + 3 + abuf->len,
buf->len - 3 - abuf->len, &hostname);
@ -436,6 +453,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
ret = tls_protocol->parse_packet(buf->data + 3 + abuf->len,
buf->len - 3 - abuf->len, &hostname);
if (ret == -1 && buf->len < BUF_SIZE && server->stage != STAGE_SNI) {
if (server_handshake_reply(EV_A_ w, 0, &response) < 0) return -1;
server->stage = STAGE_SNI;
ev_timer_start(EV_A_ & server->delayed_connect_watcher);
return -1;
@ -449,6 +467,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
}
}
if (server_handshake_reply(EV_A_ w, 0, &response) < 0) return -1;
server->stage = STAGE_STREAM;
buf->len -= (3 + abuf_len);
@ -457,11 +476,11 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
}
if (verbose) {
if (sni_detected || atyp == 3)
if (sni_detected || atyp == SOCKS5_ATYP_DOMAIN)
LOGI("connect to %s:%s", host, port);
else if (atyp == 1)
else if (atyp == SOCKS5_ATYP_IPV4)
LOGI("connect to %s:%s", ip, port);
else if (atyp == 4)
else if (atyp == SOCKS5_ATYP_IPV6)
LOGI("connect to [%s]:%s", ip, port);
}
@ -471,15 +490,13 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
#endif
) {
int bypass = 0;
#ifndef __ANDROID__
int resolved = 0;
#endif
struct sockaddr_storage storage;
memset(&storage, 0, sizeof(struct sockaddr_storage));
int err;
int host_match = 0;
if (sni_detected || atyp == 3)
if (sni_detected || atyp == SOCKS5_ATYP_DOMAIN)
host_match = acl_match_host(host);
if (host_match > 0)
@ -487,59 +504,55 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
else if (host_match < 0)
bypass = 0; // proxy hostnames in white list
else {
#ifndef __ANDROID__
if (atyp == 3) { // resolve domain so we can bypass domain with geoip
err = get_sockaddr(host, port, &storage, 0, ipv6first);
if (err != -1) {
resolved = 1;
switch (((struct sockaddr *)&storage)->sa_family) {
case AF_INET:
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)&storage;
inet_ntop(AF_INET, &(addr_in->sin_addr), ip, INET_ADDRSTRLEN);
break;
}
case AF_INET6:
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&storage;
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip, INET6_ADDRSTRLEN);
break;
}
default:
break;
}
if (atyp == SOCKS5_ATYP_DOMAIN) { // resolve domain so we can bypass domain with geoip
if (get_sockaddr(host, port, &storage, 0, ipv6first)) goto not_bypass;
resolved = 1;
switch (((struct sockaddr *)&storage)->sa_family) {
case AF_INET:
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)&storage;
if (!inet_ntop(AF_INET, &(addr_in->sin_addr), ip, INET_ADDRSTRLEN)) goto not_bypass;
break;
}
case AF_INET6:
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&storage;
if (!inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip, INET6_ADDRSTRLEN)) goto not_bypass;
break;
}
default:
goto not_bypass;
}
}
#endif
int ip_match = acl_match_host(ip);
switch (get_acl_mode()) {
case BLACK_LIST:
if (ip_match > 0)
bypass = 1; // bypass IPs in black list
break;
case WHITE_LIST:
bypass = 1;
if (ip_match < 0)
bypass = 0; // proxy IPs in white list
break;
if (resolved) {
int ip_match = acl_match_host(ip);
switch (get_acl_mode()) {
case BLACK_LIST:
if (ip_match > 0)
bypass = 1; // bypass IPs in black list
break;
case WHITE_LIST:
bypass = 1;
if (ip_match < 0)
bypass = 0; // proxy IPs in white list
break;
}
}
}
if (bypass) {
if (verbose) {
if (sni_detected || atyp == 3)
if (sni_detected || atyp == SOCKS5_ATYP_DOMAIN)
LOGI("bypass %s:%s", host, port);
else if (atyp == 1)
LOGI("bypass %s:%s", ip, port);
else if (atyp == 4)
LOGI("bypass [%s]:%s", ip, port);
}
#ifndef __ANDROID__
if (atyp == 3 && resolved != 1)
if (atyp == SOCKS5_ATYP_DOMAIN && !resolved)
err = get_sockaddr(host, port, &storage, 0, ipv6first);
else
#endif
err = get_sockaddr(ip, port, &storage, 0, ipv6first);
err = get_sockaddr(ip, port, &storage, 0, ipv6first);
if (err != -1) {
remote = create_remote(server->listener, (struct sockaddr *)&storage);
if (remote != NULL)
@ -548,6 +561,7 @@ server_handshake(EV_P_ ev_io *w, buffer_t *buf)
}
}
not_bypass:
// Not bypass
if (remote == NULL) {
remote = create_remote(server->listener, NULL);
@ -897,7 +911,6 @@ server_recv_cb(EV_P_ ev_io *w, int revents)
buf->len = 0;
return;
} else if (server->stage == STAGE_HANDSHAKE ||
server->stage == STAGE_PARSE ||
server->stage == STAGE_SNI) {
int ret = server_handshake(EV_A_ w, buf);
if (ret)

25
src/socks5.h

@ -24,13 +24,28 @@
#define _SOCKS5_H
#define SVERSION 0x05
#define CONNECT 0x01
#define IPV4 0x01
#define DOMAIN 0x03
#define IPV6 0x04
#define METHOD_NOAUTH 0x00
#define METHOD_UNACCEPTABLE 0xff
#define CMD_NOT_SUPPORTED 0x07
// see also: https://www.ietf.org/rfc/rfc1928.txt
#define SOCKS5_CMD_CONNECT 0x01
#define SOCKS5_CMD_BIND 0x02
#define SOCKS5_CMD_UDP_ASSOCIATE 0x02
#define SOCKS5_ATYP_IPV4 0x01
#define SOCKS5_ATYP_DOMAIN 0x03
#define SOCKS5_ATYP_IPV6 0x04
#define SOCKS5_REP_SUCCEEDED 0x00
#define SOCKS5_REP_GENERAL 0x01
#define SOCKS5_REP_CONN_DISALLOWED 0x02
#define SOCKS5_REP_NETWORK_UNREACHABLE 0x03
#define SOCKS5_REP_HOST_UNREACHABLE 0x04
#define SOCKS5_REP_CONN_REFUSED 0x05
#define SOCKS5_REP_TTL_EXPIRED 0x06
#define SOCKS5_REP_CMD_NOT_SUPPORTED 0x07
#define SOCKS5_REP_ADDRTYPE_NOT_SUPPORTED 0x08
#define SOCKS5_REP_FF_UNASSIGNED 0x09
struct method_select_request {
unsigned char ver;

Loading…
Cancel
Save