Rewrite socks to async
Not quite working yet, however it's a massive and important change.
This commit is contained in:
879
common.c
879
common.c
@@ -229,7 +229,7 @@ connector_step (struct connector *self)
|
||||
self->on_connected (self->user_data, fd);
|
||||
return;
|
||||
}
|
||||
else if (errno != EINPROGRESS)
|
||||
if (errno != EINPROGRESS)
|
||||
{
|
||||
connector_notify_error (self, strerror (errno));
|
||||
xclose (fd);
|
||||
@@ -324,15 +324,14 @@ connector_add_target (struct connector *self,
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- SOCKS 5/4a (blocking implementation) ------------------------------------
|
||||
// --- SOCKS 5/4a --------------------------------------------------------------
|
||||
|
||||
// These are awkward protocols. Note that the `username' is used differently
|
||||
// in SOCKS 4a and 5. In the former version, it is the username that you can
|
||||
// get ident'ed against. In the latter version, it forms a pair with the
|
||||
// password field and doesn't need to be an actual user on your machine.
|
||||
// Asynchronous SOCKS connector. Adds more stuff on top of the original.
|
||||
|
||||
// TODO: make a non-blocking poller-based version of this;
|
||||
// either use c-ares or (even better) start another thread to do resolution
|
||||
// Note that the `username' is used differently in SOCKS 4a and 5. In the
|
||||
// former version, it is the username that you can get ident'ed against.
|
||||
// In the latter version, it forms a pair with the password field and doesn't
|
||||
// need to be an actual user on your machine.
|
||||
|
||||
struct socks_addr
|
||||
{
|
||||
@@ -346,125 +345,183 @@ struct socks_addr
|
||||
union
|
||||
{
|
||||
uint8_t ipv4[4]; ///< IPv4 address, network octet order
|
||||
const char *domain; ///< Domain name
|
||||
char *domain; ///< Domain name
|
||||
uint8_t ipv6[16]; ///< IPv6 address, network octet order
|
||||
}
|
||||
data; ///< The address itself
|
||||
};
|
||||
|
||||
struct socks_data
|
||||
static void
|
||||
socks_addr_free (struct socks_addr *self)
|
||||
{
|
||||
struct socks_addr address; ///< Target address
|
||||
uint16_t port; ///< Target port
|
||||
const char *username; ///< Authentication username
|
||||
const char *password; ///< Authentication password
|
||||
if (self->type == SOCKS_DOMAIN)
|
||||
free (self->data.domain);
|
||||
}
|
||||
|
||||
struct socks_addr bound_address; ///< Bound address at the server
|
||||
uint16_t bound_port; ///< Bound port at the server
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
struct socks_target
|
||||
{
|
||||
LIST_HEADER (struct socks_target)
|
||||
|
||||
struct socks_addr address; ///< Target address
|
||||
uint16_t port; ///< Target service port
|
||||
};
|
||||
|
||||
static bool
|
||||
socks_get_socket (struct addrinfo *addresses, int *fd, struct error **e)
|
||||
enum socks_protocol
|
||||
{
|
||||
int sockfd;
|
||||
for (; addresses; addresses = addresses->ai_next)
|
||||
{
|
||||
sockfd = socket (addresses->ai_family,
|
||||
addresses->ai_socktype, addresses->ai_protocol);
|
||||
if (sockfd == -1)
|
||||
continue;
|
||||
set_cloexec (sockfd);
|
||||
SOCKS_5, ///< SOCKS5
|
||||
SOCKS_4A, ///< SOCKS4A
|
||||
SOCKS_MAX ///< End of protocol
|
||||
};
|
||||
|
||||
int yes = 1;
|
||||
soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
|
||||
&yes, sizeof yes) != -1);
|
||||
struct socks_connector
|
||||
{
|
||||
struct connector *connector; ///< Proxy server iterator (effectively)
|
||||
enum socks_protocol protocol_iter; ///< Protocol iterator
|
||||
struct socks_target *targets_iter; ///< Targets iterator
|
||||
|
||||
if (!connect (sockfd, addresses->ai_addr, addresses->ai_addrlen))
|
||||
break;
|
||||
xclose (sockfd);
|
||||
}
|
||||
if (!addresses)
|
||||
{
|
||||
error_set (e, "couldn't connect to the SOCKS server");
|
||||
return false;
|
||||
}
|
||||
*fd = sockfd;
|
||||
return true;
|
||||
}
|
||||
// Negotiation:
|
||||
|
||||
int socket_fd; ///< Current socket file descriptor
|
||||
struct poller_fd socket_event; ///< Socket can be read from/written to
|
||||
struct str read_buffer; ///< Read buffer
|
||||
struct str write_buffer; ///< Write buffer
|
||||
|
||||
struct poller_timer timeout; ///< Timeout timer
|
||||
|
||||
uint8_t bound_address_len; ///< Length of domain name
|
||||
struct socks_addr bound_address; ///< Bound address at the server
|
||||
uint16_t bound_port; ///< Bound port at the server
|
||||
|
||||
/// Process incoming data if there's enough of it available
|
||||
bool (*on_data) (struct socks_connector *);
|
||||
|
||||
// Configuration:
|
||||
|
||||
const char *hostname; ///< SOCKS server hostname
|
||||
const char *service; ///< SOCKS server service name or port
|
||||
|
||||
const char *username; ///< Username for authentication
|
||||
const char *password; ///< Password for authentication
|
||||
|
||||
struct socks_target *targets; ///< Targets
|
||||
struct socks_target *targets_tail; ///< Tail of targets
|
||||
|
||||
void *user_data; ///< User data for callbacks
|
||||
|
||||
// You may destroy the connector object in these two main callbacks:
|
||||
|
||||
/// Connection has been successfully established
|
||||
void (*on_connected) (void *user_data, int socket);
|
||||
/// Failed to establish a connection to either target
|
||||
void (*on_failure) (void *user_data);
|
||||
|
||||
// Optional:
|
||||
|
||||
/// Connecting to a new address
|
||||
void (*on_connecting) (void *user_data,
|
||||
const char *address, const char *via, const char *version);
|
||||
/// Connecting to the last address has failed
|
||||
void (*on_error) (void *user_data, const char *error);
|
||||
};
|
||||
|
||||
#define SOCKS_FAIL(...) \
|
||||
BLOCK_START \
|
||||
error_set (e, __VA_ARGS__); \
|
||||
goto fail; \
|
||||
char *error = xstrdup_printf (__VA_ARGS__); \
|
||||
if (self->on_error) \
|
||||
self->on_error (self->user_data, error); \
|
||||
free (error); \
|
||||
return false; \
|
||||
BLOCK_END
|
||||
#define SOCKS_RECV(buf, len) \
|
||||
|
||||
// FIXME: we need to cancel all events
|
||||
#define SOCKS_DONE() \
|
||||
BLOCK_START \
|
||||
if ((n = recv (sockfd, (buf), (len), 0)) == -1) \
|
||||
SOCKS_FAIL ("%s: %s", "recv", strerror (errno)); \
|
||||
if (n != (len)) \
|
||||
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF"); \
|
||||
int fd = self->socket_fd; \
|
||||
set_blocking (fd, true); \
|
||||
self->socket_fd = -1; \
|
||||
self->on_connected (self->user_data, fd); \
|
||||
return true; \
|
||||
BLOCK_END
|
||||
|
||||
#define SOCKS_NEED_DATA(n) \
|
||||
if (!socks_try_fill_read_buffer (self, (n))) \
|
||||
return false; \
|
||||
if (self->read_buffer.len < n) \
|
||||
return true
|
||||
|
||||
static bool
|
||||
socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||
int *fd, struct error **e)
|
||||
socks_try_fill_read_buffer (struct socks_connector *self, size_t n)
|
||||
{
|
||||
int sockfd;
|
||||
if (!socks_get_socket (addresses, &sockfd, e))
|
||||
return false;
|
||||
ssize_t remains = (ssize_t) n - (ssize_t) self->read_buffer.len;
|
||||
if (remains <= 0)
|
||||
return true;
|
||||
|
||||
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
||||
const char *dest_domain = NULL;
|
||||
ssize_t received;
|
||||
str_ensure_space (&self->read_buffer, remains);
|
||||
do
|
||||
received = recv (self->socket_fd,
|
||||
self->read_buffer.str + self->read_buffer.len, remains, 0);
|
||||
while ((received == -1) && errno == EINTR);
|
||||
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
switch (data->address.type)
|
||||
if (received == 0)
|
||||
SOCKS_FAIL ("%s: %s", "protocol error", "unexpected EOF");
|
||||
if (received == -1 && errno != EAGAIN)
|
||||
SOCKS_FAIL ("%s: %s", "recv", strerror (errno));
|
||||
if (received > 0)
|
||||
self->read_buffer.len += received;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_try_flush_write_buffer (struct socks_connector *self)
|
||||
{
|
||||
struct str *wb = &self->write_buffer;
|
||||
ssize_t n_written;
|
||||
|
||||
while (wb->len)
|
||||
{
|
||||
case SOCKS_IPV4:
|
||||
dest_ipv4 = data->address.data.ipv4;
|
||||
break;
|
||||
case SOCKS_IPV6:
|
||||
// About the best thing we can do, not sure if it works anywhere at all
|
||||
if (!inet_ntop (AF_INET6, &data->address.data.ipv6, buf, sizeof buf))
|
||||
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
|
||||
dest_domain = buf;
|
||||
break;
|
||||
case SOCKS_DOMAIN:
|
||||
dest_domain = data->address.data.domain;
|
||||
}
|
||||
n_written = send (self->socket_fd, wb->str, wb->len, 0);
|
||||
if (n_written >= 0)
|
||||
{
|
||||
str_remove_slice (wb, 0, n_written);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct str req;
|
||||
str_init (&req);
|
||||
str_append_c (&req, 4); // version
|
||||
str_append_c (&req, 1); // connect
|
||||
if (errno == EAGAIN)
|
||||
break;
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
str_append_c (&req, data->port >> 8); // higher bits of port
|
||||
str_append_c (&req, data->port); // lower bits of port
|
||||
str_append_data (&req, dest_ipv4, 4); // destination address
|
||||
|
||||
if (data->username)
|
||||
str_append (&req, data->username);
|
||||
str_append_c (&req, '\0');
|
||||
|
||||
if (dest_domain)
|
||||
{
|
||||
str_append (&req, dest_domain);
|
||||
str_append_c (&req, '\0');
|
||||
}
|
||||
|
||||
ssize_t n = send (sockfd, req.str, req.len, 0);
|
||||
str_free (&req);
|
||||
if (n == -1)
|
||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t resp[8];
|
||||
SOCKS_RECV (resp, sizeof resp);
|
||||
if (resp[0] != 0)
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static bool
|
||||
socks_4a_finish (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (8);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
|
||||
uint8_t null, status;
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &null));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
if (null != 0)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
|
||||
switch (resp[1])
|
||||
switch (status)
|
||||
{
|
||||
case 90:
|
||||
break;
|
||||
SOCKS_DONE ();
|
||||
case 91:
|
||||
SOCKS_FAIL ("request rejected or failed");
|
||||
case 92:
|
||||
@@ -476,141 +533,138 @@ socks_4a_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||
default:
|
||||
SOCKS_FAIL ("protocol error");
|
||||
}
|
||||
|
||||
*fd = sockfd;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
xclose (sockfd);
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef SOCKS_FAIL
|
||||
#define SOCKS_FAIL(...) \
|
||||
BLOCK_START \
|
||||
error_set (e, __VA_ARGS__); \
|
||||
return false; \
|
||||
BLOCK_END
|
||||
|
||||
static bool
|
||||
socks_5_userpass_auth (int sockfd, struct socks_data *data, struct error **e)
|
||||
{
|
||||
size_t ulen = strlen (data->username);
|
||||
if (ulen > 255)
|
||||
ulen = 255;
|
||||
|
||||
size_t plen = strlen (data->password);
|
||||
if (plen > 255)
|
||||
plen = 255;
|
||||
|
||||
uint8_t req[3 + ulen + plen], *p = req;
|
||||
*p++ = 0x01; // version
|
||||
*p++ = ulen; // username length
|
||||
memcpy (p, data->username, ulen);
|
||||
p += ulen;
|
||||
*p++ = plen; // password length
|
||||
memcpy (p, data->password, plen);
|
||||
p += plen;
|
||||
|
||||
ssize_t n = send (sockfd, req, p - req, 0);
|
||||
if (n == -1)
|
||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||
|
||||
uint8_t resp[2];
|
||||
SOCKS_RECV (resp, sizeof resp);
|
||||
if (resp[0] != 0x01)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
if (resp[1] != 0x00)
|
||||
SOCKS_FAIL ("authentication failure");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_auth (int sockfd, struct socks_data *data, struct error **e)
|
||||
socks_4a_start (struct socks_connector *self)
|
||||
{
|
||||
bool can_auth = data->username && data->password;
|
||||
struct socks_target *target = self->targets_iter;
|
||||
const void *dest_ipv4 = "\x00\x00\x00\x01";
|
||||
const char *dest_domain = NULL;
|
||||
|
||||
uint8_t hello[4];
|
||||
hello[0] = 0x05; // version
|
||||
hello[1] = 1 + can_auth; // number of authentication methods
|
||||
hello[2] = 0x00; // no authentication required
|
||||
hello[3] = 0x02; // username/password
|
||||
|
||||
ssize_t n = send (sockfd, hello, 3 + can_auth, 0);
|
||||
if (n == -1)
|
||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||
|
||||
uint8_t resp[2];
|
||||
SOCKS_RECV (resp, sizeof resp);
|
||||
if (resp[0] != 0x05)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
|
||||
switch (resp[1])
|
||||
{
|
||||
case 0x02:
|
||||
if (!can_auth)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
if (!socks_5_userpass_auth (sockfd, data, e))
|
||||
return false;
|
||||
case 0x00:
|
||||
break;
|
||||
case 0xFF:
|
||||
SOCKS_FAIL ("no acceptable authentication methods");
|
||||
default:
|
||||
SOCKS_FAIL ("protocol error");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_send_req (int sockfd, struct socks_data *data, struct error **e)
|
||||
{
|
||||
uint8_t req[4 + 256 + 2], *p = req;
|
||||
*p++ = 0x05; // version
|
||||
*p++ = 0x01; // connect
|
||||
*p++ = 0x00; // reserved
|
||||
*p++ = data->address.type;
|
||||
|
||||
switch (data->address.type)
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
switch (target->address.type)
|
||||
{
|
||||
case SOCKS_IPV4:
|
||||
memcpy (p, data->address.data.ipv4, sizeof data->address.data.ipv4);
|
||||
p += sizeof data->address.data.ipv4;
|
||||
dest_ipv4 = target->address.data.ipv4;
|
||||
break;
|
||||
case SOCKS_IPV6:
|
||||
// About the best thing we can do, not sure if it works anywhere at all
|
||||
if (!inet_ntop (AF_INET6, &target->address.data.ipv6, buf, sizeof buf))
|
||||
SOCKS_FAIL ("%s: %s", "inet_ntop", strerror (errno));
|
||||
dest_domain = buf;
|
||||
break;
|
||||
case SOCKS_DOMAIN:
|
||||
dest_domain = target->address.data.domain;
|
||||
}
|
||||
|
||||
struct str *wb = &self->write_buffer;
|
||||
str_init (wb);
|
||||
str_append_c (wb, 4); // version
|
||||
str_append_c (wb, 1); // connect
|
||||
|
||||
str_append_c (wb, target->port >> 8); // higher bits of port
|
||||
str_append_c (wb, target->port); // lower bits of port
|
||||
str_append_data (wb, dest_ipv4, 4); // destination address
|
||||
|
||||
if (self->username)
|
||||
str_append (wb, self->username);
|
||||
str_append_c (wb, '\0');
|
||||
|
||||
if (dest_domain)
|
||||
{
|
||||
size_t dlen = strlen (data->address.data.domain);
|
||||
if (dlen > 255)
|
||||
dlen = 255;
|
||||
|
||||
*p++ = dlen;
|
||||
memcpy (p, data->address.data.domain, dlen);
|
||||
p += dlen;
|
||||
break;
|
||||
str_append (wb, dest_domain);
|
||||
str_append_c (wb, '\0');
|
||||
}
|
||||
case SOCKS_IPV6:
|
||||
memcpy (p, data->address.data.ipv6, sizeof data->address.data.ipv6);
|
||||
p += sizeof data->address.data.ipv6;
|
||||
break;
|
||||
}
|
||||
*p++ = data->port >> 8;
|
||||
*p++ = data->port;
|
||||
|
||||
if (send (sockfd, req, p - req, 0) == -1)
|
||||
SOCKS_FAIL ("%s: %s", "send", strerror (errno));
|
||||
self->on_data = socks_4a_finish;
|
||||
return true;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static bool
|
||||
socks_5_request_port (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (2);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
hard_assert (msg_unpacker_u16 (&unpacker, &self->bound_port));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
SOCKS_DONE ();
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_request_ipv4 (struct socks_connector *self)
|
||||
{
|
||||
size_t len = sizeof self->bound_address.data.ipv4;
|
||||
SOCKS_NEED_DATA (len);
|
||||
memcpy (self->bound_address.data.ipv4, self->read_buffer.str, len);
|
||||
str_remove_slice (&self->read_buffer, 0, len);
|
||||
|
||||
self->on_data = socks_5_request_port;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
||||
socks_5_request_ipv6 (struct socks_connector *self)
|
||||
{
|
||||
uint8_t resp_header[4];
|
||||
ssize_t n;
|
||||
SOCKS_RECV (resp_header, sizeof resp_header);
|
||||
if (resp_header[0] != 0x05)
|
||||
size_t len = sizeof self->bound_address.data.ipv6;
|
||||
SOCKS_NEED_DATA (len);
|
||||
memcpy (self->bound_address.data.ipv6, self->read_buffer.str, len);
|
||||
str_remove_slice (&self->read_buffer, 0, len);
|
||||
|
||||
self->on_data = socks_5_request_port;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_request_domain_data (struct socks_connector *self)
|
||||
{
|
||||
size_t len = self->bound_address_len;
|
||||
SOCKS_NEED_DATA (len);
|
||||
self->bound_address.data.domain = xstrndup (self->read_buffer.str, len);
|
||||
str_remove_slice (&self->read_buffer, 0, len);
|
||||
|
||||
self->on_data = socks_5_request_port;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_request_domain (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (1);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &self->bound_address_len));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
self->on_data = socks_5_request_domain_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_request_finish (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (4);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
|
||||
uint8_t version, status, reserved, type;
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &reserved));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &type));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
if (version != 0x05)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
|
||||
switch (resp_header[1])
|
||||
switch (status)
|
||||
{
|
||||
case 0x00:
|
||||
break;
|
||||
@@ -625,109 +679,368 @@ socks_5_process_resp (int sockfd, struct socks_data *data, struct error **e)
|
||||
default: SOCKS_FAIL ("protocol error");
|
||||
}
|
||||
|
||||
switch ((data->bound_address.type = resp_header[3]))
|
||||
switch ((self->bound_address.type = type))
|
||||
{
|
||||
case SOCKS_IPV4: self->on_data = socks_5_request_ipv4; return true;
|
||||
case SOCKS_IPV6: self->on_data = socks_5_request_ipv6; return true;
|
||||
case SOCKS_DOMAIN: self->on_data = socks_5_request_domain; return true;
|
||||
default: SOCKS_FAIL ("protocol error");
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_request_start (struct socks_connector *self)
|
||||
{
|
||||
struct socks_target *target = self->targets_iter;
|
||||
struct str *wb = &self->write_buffer;
|
||||
str_append_c (wb, 0x05); // version
|
||||
str_append_c (wb, 0x01); // connect
|
||||
str_append_c (wb, 0x00); // reserved
|
||||
str_append_c (wb, target->address.type);
|
||||
|
||||
switch (target->address.type)
|
||||
{
|
||||
case SOCKS_IPV4:
|
||||
SOCKS_RECV (data->bound_address.data.ipv4,
|
||||
sizeof data->bound_address.data.ipv4);
|
||||
break;
|
||||
case SOCKS_IPV6:
|
||||
SOCKS_RECV (data->bound_address.data.ipv6,
|
||||
sizeof data->bound_address.data.ipv6);
|
||||
str_append_data (wb,
|
||||
target->address.data.ipv4, sizeof target->address.data.ipv4);
|
||||
break;
|
||||
case SOCKS_DOMAIN:
|
||||
{
|
||||
uint8_t len;
|
||||
SOCKS_RECV (&len, sizeof len);
|
||||
size_t dlen = strlen (target->address.data.domain);
|
||||
if (dlen > 255)
|
||||
dlen = 255;
|
||||
|
||||
char domain[len + 1];
|
||||
SOCKS_RECV (domain, len);
|
||||
domain[len] = '\0';
|
||||
|
||||
data->bound_address.data.domain = xstrdup (domain);
|
||||
str_append_c (wb, dlen);
|
||||
str_append_data (wb, target->address.data.domain, dlen);
|
||||
break;
|
||||
}
|
||||
case SOCKS_IPV6:
|
||||
str_append_data (wb,
|
||||
target->address.data.ipv6, sizeof target->address.data.ipv6);
|
||||
break;
|
||||
}
|
||||
str_append_c (wb, target->port >> 8);
|
||||
str_append_c (wb, target->port);
|
||||
|
||||
self->on_data = socks_5_request_finish;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_userpass_finish (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (2);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
|
||||
uint8_t version, status;
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &status));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
if (version != 0x01)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
if (status != 0x00)
|
||||
SOCKS_FAIL ("authentication failure");
|
||||
return socks_5_request_start (self);
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_userpass_start (struct socks_connector *self)
|
||||
{
|
||||
size_t ulen = strlen (self->username);
|
||||
if (ulen > 255)
|
||||
ulen = 255;
|
||||
|
||||
size_t plen = strlen (self->password);
|
||||
if (plen > 255)
|
||||
plen = 255;
|
||||
|
||||
struct str *wb = &self->write_buffer;
|
||||
str_append_c (wb, 0x01); // version
|
||||
str_append_c (wb, ulen); // username length
|
||||
str_append_data (wb, self->username, ulen);
|
||||
str_append_c (wb, plen); // password length
|
||||
str_append_data (wb, self->password, plen);
|
||||
|
||||
self->on_data = socks_5_userpass_finish;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_5_auth_finish (struct socks_connector *self)
|
||||
{
|
||||
SOCKS_NEED_DATA (2);
|
||||
|
||||
struct msg_unpacker unpacker;
|
||||
msg_unpacker_init (&unpacker, self->read_buffer.str, self->read_buffer.len);
|
||||
|
||||
uint8_t version, method;
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &version));
|
||||
hard_assert (msg_unpacker_u8 (&unpacker, &method));
|
||||
str_remove_slice (&self->read_buffer, 0, unpacker.offset);
|
||||
|
||||
if (version != 0x05)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
|
||||
bool can_auth = self->username && self->password;
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case 0x02:
|
||||
if (!can_auth)
|
||||
SOCKS_FAIL ("protocol error");
|
||||
|
||||
return socks_5_userpass_start (self);
|
||||
case 0x00:
|
||||
return socks_5_request_start (self);
|
||||
case 0xFF:
|
||||
SOCKS_FAIL ("no acceptable authentication methods");
|
||||
default:
|
||||
SOCKS_FAIL ("protocol error");
|
||||
}
|
||||
|
||||
uint16_t port;
|
||||
SOCKS_RECV (&port, sizeof port);
|
||||
data->bound_port = ntohs (port);
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef SOCKS_FAIL
|
||||
#undef SOCKS_RECV
|
||||
|
||||
static bool
|
||||
socks_5_connect (struct addrinfo *addresses, struct socks_data *data,
|
||||
int *fd, struct error **e)
|
||||
socks_5_auth_start (struct socks_connector *self)
|
||||
{
|
||||
int sockfd;
|
||||
if (!socks_get_socket (addresses, &sockfd, e))
|
||||
return false;
|
||||
bool can_auth = self->username && self->password;
|
||||
|
||||
if (!socks_5_auth (sockfd, data, e)
|
||||
|| !socks_5_send_req (sockfd, data, e)
|
||||
|| !socks_5_process_resp (sockfd, data, e))
|
||||
{
|
||||
xclose (sockfd);
|
||||
return false;
|
||||
}
|
||||
struct str *wb = &self->write_buffer;
|
||||
str_append_c (wb, 0x05); // version
|
||||
str_append_c (wb, 1 + can_auth); // number of authentication methods
|
||||
str_append_c (wb, 0x00); // no authentication required
|
||||
str_append_c (wb, 0x02); // username/password
|
||||
|
||||
*fd = sockfd;
|
||||
self->on_data = socks_5_auth_finish;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
socks_connect (const char *socks_host, const char *socks_port,
|
||||
const char *host, const char *port,
|
||||
const char *username, const char *password, struct error **e)
|
||||
{
|
||||
int result = -1;
|
||||
struct addrinfo gai_hints, *gai_result;
|
||||
memset (&gai_hints, 0, sizeof gai_hints);
|
||||
gai_hints.ai_socktype = SOCK_STREAM;
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
unsigned long port_no;
|
||||
static void socks_connector_step (struct socks_connector *self);
|
||||
|
||||
static void
|
||||
socks_connector_on_connected (void *user_data, int socket_fd)
|
||||
{
|
||||
set_blocking (socket_fd, false);
|
||||
|
||||
struct socks_connector *self = user_data;
|
||||
self->socket_fd = socket_fd;
|
||||
self->socket_event.fd = socket_fd;
|
||||
poller_fd_set (&self->socket_event, POLLIN | POLLOUT);
|
||||
str_reset (&self->read_buffer);
|
||||
str_reset (&self->write_buffer);
|
||||
|
||||
if ((self->protocol_iter == SOCKS_5 && socks_5_auth_start (self))
|
||||
|| (self->protocol_iter == SOCKS_4A && socks_4a_start (self)))
|
||||
return;
|
||||
|
||||
self->on_failure (self->user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_on_failure (void *user_data)
|
||||
{
|
||||
struct socks_connector *self = user_data;
|
||||
// TODO: skip SOCKS server on connection failure
|
||||
socks_connector_step (self);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_on_connecting (void *user_data, const char *via)
|
||||
{
|
||||
struct socks_connector *self = user_data;
|
||||
if (!self->on_connecting)
|
||||
return;
|
||||
|
||||
// TODO: reconstruct the address from the current target iterator,
|
||||
// or just store it in unprocessed form
|
||||
char *address = format_host_port_pair ("", "");
|
||||
self->on_connecting (self->user_data, address, via,
|
||||
self->protocol_iter ? "SOCKS4A" : "SOCKS5");
|
||||
free (address);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_on_error (void *user_data, const char *error)
|
||||
{
|
||||
struct socks_connector *self = user_data;
|
||||
// TODO: skip protocol on protocol failure
|
||||
self->on_error (self->user_data, error);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_start (struct socks_connector *self)
|
||||
{
|
||||
struct connector *connector =
|
||||
self->connector = xcalloc (1, sizeof *connector);
|
||||
connector_init (connector, self->socket_event.poller);
|
||||
|
||||
connector->user_data = self;
|
||||
connector->on_connected = socks_connector_on_connected;
|
||||
connector->on_connecting = socks_connector_on_connecting;
|
||||
connector->on_error = socks_connector_on_error;
|
||||
connector->on_failure = socks_connector_on_failure;
|
||||
|
||||
// TODO: let's rather call on_error and on_failure instead on error
|
||||
hard_assert (connector_add_target (connector,
|
||||
self->hostname, self->service, NULL));
|
||||
|
||||
connector_step (connector);
|
||||
poller_timer_set (&self->timeout, 60 * 1000);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_step (struct socks_connector *self)
|
||||
{
|
||||
// Destroy current connector if needed
|
||||
if (self->connector)
|
||||
{
|
||||
connector_free (self->connector);
|
||||
free (self->connector);
|
||||
self->connector = NULL;
|
||||
}
|
||||
|
||||
// At the lowest level we iterate over all addresses for the SOCKS server;
|
||||
// this is done automatically by the connector
|
||||
|
||||
// Then we iterate over available protocols
|
||||
if (++self->protocol_iter != SOCKS_MAX)
|
||||
{
|
||||
socks_connector_start (self);
|
||||
return;
|
||||
}
|
||||
|
||||
// At the highest level we iterate over possible targets
|
||||
self->protocol_iter = 0;
|
||||
if (self->targets_iter && (self->targets_iter = self->targets_iter->next))
|
||||
{
|
||||
socks_connector_start (self);
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: we need to cancel all events
|
||||
self->on_failure (self->user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_on_ready
|
||||
(const struct pollfd *pfd, struct socks_connector *self)
|
||||
{
|
||||
(void) pfd;
|
||||
|
||||
if (!self->on_data (self) || !socks_try_flush_write_buffer (self))
|
||||
{
|
||||
// We've failed this target, let's try to move on
|
||||
// FIXME: we need to cancel all events
|
||||
socks_connector_step (self);
|
||||
}
|
||||
// If we successfully establish the connection, then the FD is reset to -1
|
||||
else if (self->socket_fd != -1)
|
||||
{
|
||||
poller_fd_set (&self->socket_event,
|
||||
self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_on_timeout (struct socks_connector *self)
|
||||
{
|
||||
if (self->on_error)
|
||||
self->on_error (self->user_data, "timeout");
|
||||
|
||||
// FIXME: we need to cancel all events
|
||||
self->on_failure (self->user_data);
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
socks_connector_init (struct socks_connector *self, struct poller *poller)
|
||||
{
|
||||
memset (self, 0, sizeof *self);
|
||||
|
||||
poller_fd_init (&self->socket_event, poller, (self->socket_fd = -1));
|
||||
self->socket_event.dispatcher = (poller_fd_fn) socks_connector_on_ready;
|
||||
self->socket_event.user_data = self;
|
||||
|
||||
poller_timer_init (&self->timeout, poller);
|
||||
self->timeout.dispatcher = (poller_timer_fn) socks_connector_on_timeout;
|
||||
self->timeout.user_data = self;
|
||||
|
||||
str_init (&self->read_buffer);
|
||||
str_init (&self->write_buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
socks_connector_free (struct socks_connector *self)
|
||||
{
|
||||
if (self->connector)
|
||||
{
|
||||
connector_free (self->connector);
|
||||
free (self->connector);
|
||||
}
|
||||
|
||||
poller_fd_reset (&self->socket_event);
|
||||
poller_timer_reset (&self->timeout);
|
||||
|
||||
if (self->socket_fd != -1)
|
||||
xclose (self->socket_fd);
|
||||
|
||||
str_free (&self->read_buffer);
|
||||
str_free (&self->write_buffer);
|
||||
|
||||
LIST_FOR_EACH (struct socks_target, iter, self->targets)
|
||||
{
|
||||
socks_addr_free (&iter->address);
|
||||
free (iter);
|
||||
}
|
||||
|
||||
socks_addr_free (&self->bound_address);
|
||||
}
|
||||
|
||||
static bool
|
||||
socks_connector_add_target (struct socks_connector *self,
|
||||
const char *host, const char *service, struct error **e)
|
||||
{
|
||||
unsigned long port;
|
||||
const struct servent *serv;
|
||||
if ((serv = getservbyname (port, "tcp")))
|
||||
port_no = (uint16_t) ntohs (serv->s_port);
|
||||
else if (!xstrtoul (&port_no, port, 10) || !port_no || port_no > UINT16_MAX)
|
||||
if ((serv = getservbyname (service, "tcp")))
|
||||
port = (uint16_t) ntohs (serv->s_port);
|
||||
else if (!xstrtoul (&port, service, 10) || !port || port > UINT16_MAX)
|
||||
{
|
||||
error_set (e, "invalid port number");
|
||||
goto fail;
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = getaddrinfo (socks_host, socks_port, &gai_hints, &gai_result);
|
||||
if (err)
|
||||
{
|
||||
error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
struct socks_data data =
|
||||
{ .username = username, .password = password, .port = port_no };
|
||||
|
||||
if (inet_pton (AF_INET, host, &data.address.data.ipv4) == 1)
|
||||
data.address.type = SOCKS_IPV4;
|
||||
else if (inet_pton (AF_INET6, host, &data.address.data.ipv6) == 1)
|
||||
data.address.type = SOCKS_IPV6;
|
||||
struct socks_target *target = xcalloc (1, sizeof *target);
|
||||
if (inet_pton (AF_INET, host, &target->address.data.ipv4) == 1)
|
||||
target->address.type = SOCKS_IPV4;
|
||||
else if (inet_pton (AF_INET6, host, &target->address.data.ipv6) == 1)
|
||||
target->address.type = SOCKS_IPV6;
|
||||
else
|
||||
{
|
||||
data.address.type = SOCKS_DOMAIN;
|
||||
data.address.data.domain = host;
|
||||
target->address.type = SOCKS_DOMAIN;
|
||||
target->address.data.domain = xstrdup (host);
|
||||
}
|
||||
|
||||
if (!socks_5_connect (gai_result, &data, &result, NULL))
|
||||
socks_4a_connect (gai_result, &data, &result, e);
|
||||
target->port = port;
|
||||
LIST_APPEND_WITH_TAIL (self->targets, self->targets_tail, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.bound_address.type == SOCKS_DOMAIN)
|
||||
free ((char *) data.bound_address.data.domain);
|
||||
freeaddrinfo (gai_result);
|
||||
fail:
|
||||
return result;
|
||||
static void
|
||||
socks_connector_run (struct socks_connector *self)
|
||||
{
|
||||
// XXX: do we need some better error checking in here?
|
||||
hard_assert (self->hostname);
|
||||
hard_assert (self->targets);
|
||||
|
||||
self->targets_iter = self->targets;
|
||||
self->protocol_iter = 0;
|
||||
socks_connector_start (self);
|
||||
}
|
||||
|
||||
// --- CTCP decoding -----------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user