@@ -41,6 +41,7 @@
#include "sysemu/sysemu.h"
#include "qemu/cutils.h"
#include "qapi/error.h"
+#include "crypto/secret.h"
static int get_str_sep(char *buf, int buf_size, const char **pp, int sep)
{
@@ -139,6 +140,33 @@ static void net_slirp_cleanup(NetClientState *nc)
QTAILQ_REMOVE(&slirp_stacks, s, entry);
}
+static int net_slirp_add_proxy(SlirpState *s, const char *proxy_server,
+ const char *proxy_user,
+ const char *proxy_secretid)
+{
+ InetSocketAddress *addr;
+ char *password = NULL;
+ int ret;
+
+ if (proxy_server == NULL) {
+ return 0;
+ }
+
+ if (proxy_secretid) {
+ password = qcrypto_secret_lookup_as_utf8(proxy_secretid, &error_fatal);
+ }
+
+ addr = inet_parse(proxy_server, &error_fatal);
+
+ ret = slirp_add_proxy(s->slirp, addr->host, atoi(addr->port),
+ proxy_user, password);
+
+ qapi_free_InetSocketAddress(addr);
+ g_free(password);
+
+ return ret;
+}
+
static NetClientInfo net_slirp_info = {
.type = NET_CLIENT_DRIVER_USER,
.size = sizeof(SlirpState),
@@ -155,7 +183,8 @@ static int net_slirp_init(NetClientState *peer, const char *model,
const char *bootfile, const char *vdhcp_start,
const char *vnameserver, const char *vnameserver6,
const char *smb_export, const char *vsmbserver,
- const char **dnssearch)
+ const char **dnssearch, const char *proxy_server,
+ const char *proxy_user, const char *proxy_secretid)
{
/* default settings according to historic slirp */
struct in_addr net = { .s_addr = htonl(0x0a000200) }; /* 10.0.2.0 */
@@ -361,6 +390,11 @@ static int net_slirp_init(NetClientState *peer, const char *model,
}
#endif
+ if (net_slirp_add_proxy(s, proxy_server,
+ proxy_user, proxy_secretid) < 0) {
+ goto error;
+ }
+
s->exit_notifier.notify = slirp_smb_exit;
qemu_add_exit_notifier(&s->exit_notifier);
return 0;
@@ -882,7 +916,8 @@ int net_init_slirp(const Netdev *netdev, const char *name,
user->ipv6_host, user->hostname, user->tftp,
user->bootfile, user->dhcpstart,
user->dns, user->ipv6_dns, user->smb,
- user->smbserver, dnssearch);
+ user->smbserver, dnssearch, user->proxy_server,
+ user->proxy_user, user->proxy_secretid);
while (slirp_configs) {
config = slirp_configs;
@@ -3661,6 +3661,12 @@
#
# @guestfwd: forward guest TCP connections
#
+# @proxy-server: address of the SOCKS5 proxy server to use (since 2.10)
+#
+# @proxy-user: username to use with the proxy server (since 2.10)
+#
+# @proxy-secretid: secret id to use for the proxy server password (since 2.10)
+#
# Since: 1.2
##
{ 'struct': 'NetdevUserOptions',
@@ -3683,6 +3689,9 @@
'*ipv6-dns': 'str',
'*smb': 'str',
'*smbserver': 'str',
+ '*proxy-server': 'str',
+ '*proxy-user': 'str',
+ '*proxy-secretid': 'str',
'*hostfwd': ['String'],
'*guestfwd': ['String'] } }
@@ -1682,6 +1682,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev,
#ifndef _WIN32
"[,smb=dir[,smbserver=addr]]\n"
#endif
+ " [,proxy-server=addr:port[,proxy-user=user,proxy-secretid=id]]\n"
" configure a user mode network backend with ID 'str',\n"
" its DHCP server and optional services\n"
#endif
@@ -1920,6 +1921,16 @@ Note that a SAMBA server must be installed on the host OS.
QEMU was tested successfully with smbd versions from Red Hat 9,
Fedora Core 3 and OpenSUSE 11.x.
+@item proxy-server=@var{addr}:@var{port}[,proxy-user=@var{user},proxy-secretid=@var{secretid}]]
+If you provide a SOCKS5 proxy server address @var{addr} and a port number @var{port},
+QEMU will use it to connect to Internet. If the proxy server needs an user id and a password
+the values are provided with proxy-user and proxy-secretid (via secret object).
+
+For example, to connect to a TOR proxy server on the host, use the following:
+@example
+qemu-system-i386 -net user,proxy-server=localhost:9050
+@end example
+
@item hostfwd=[tcp|udp]:[@var{hostaddr}]:@var{hostport}-[@var{guestaddr}]:@var{guestport}
Redirect incoming TCP or UDP connections to the host port @var{hostport} to
the guest IP address @var{guestaddr} on guest port @var{guestport}. If
@@ -2,4 +2,4 @@ common-obj-y = cksum.o if.o ip_icmp.o ip6_icmp.o ip6_input.o ip6_output.o \
ip_input.o ip_output.o dnssearch.o dhcpv6.o
common-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o
common-obj-y += tcp_subr.o tcp_timer.o udp.o udp6.o bootp.o tftp.o arp_table.o \
- ndp_table.o ncsi.o
+ ndp_table.o ncsi.o socks5.o
@@ -155,7 +155,7 @@ icmp_input(struct mbuf *m, int hlen)
if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr ||
ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) {
icmp_reflect(m);
- } else if (slirp->restricted) {
+ } else if (slirp->restricted || slirp->proxy_server) {
goto freeit;
} else {
struct socket *so;
@@ -26,6 +26,9 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error);
void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len);
+int slirp_add_proxy(Slirp *slirp, const char *server, int port,
+ const char *user, const char *password);
+
/* you must provide the following functions: */
void slirp_output(void *opaque, const uint8_t *pkt, int pkt_len);
@@ -29,6 +29,7 @@
#include "slirp.h"
#include "hw/hw.h"
#include "qemu/cutils.h"
+#include "socks5.h"
#ifndef _WIN32
#include <net/if.h>
@@ -442,6 +443,10 @@ void slirp_pollfds_fill(GArray *pollfds, uint32_t *timeout)
.fd = so->s,
.events = G_IO_OUT | G_IO_ERR,
};
+ if (so->so_proxy_state &&
+ so->so_proxy_state != SOCKS5_STATE_ERROR) {
+ pfd.events |= G_IO_IN;
+ }
so->pollfds_idx = pollfds->len;
g_array_append_val(pollfds, pfd);
continue;
@@ -617,6 +622,10 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error)
* Check sockets for reading
*/
else if (revents & (G_IO_IN | G_IO_HUP | G_IO_ERR)) {
+ if (so->so_state & SS_ISFCONNECTING) {
+ socks5_recv(so->s, &so->so_proxy_state);
+ continue;
+ }
/*
* Check for incoming connections
*/
@@ -645,11 +654,19 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error)
/*
* Check for non-blocking, still-connecting sockets
*/
- if (so->so_state & SS_ISFCONNECTING) {
- /* Connected */
- so->so_state &= ~SS_ISFCONNECTING;
- ret = send(so->s, (const void *) &ret, 0, 0);
+ if (so->so_state & SS_ISFCONNECTING) {
+ ret = socks5_send(so->s, slirp->proxy_user,
+ slirp->proxy_password, so->fhost.ss,
+ &so->so_proxy_state);
+ if (ret == 0) {
+ continue;
+ }
+ if (ret > 0) {
+ /* Connected */
+ so->so_state &= ~SS_ISFCONNECTING;
+ ret = send(so->s, (const void *) &ret, 0, 0);
+ }
if (ret < 0) {
/* XXXXX Must fix, zero bytes is a NOP */
if (errno == EAGAIN || errno == EWOULDBLOCK ||
@@ -1073,6 +1090,50 @@ int slirp_add_exec(Slirp *slirp, int do_pty, const void *args,
htons(guest_port));
}
+int slirp_add_proxy(Slirp *slirp, const char *server, int port,
+ const char *user, const char *password)
+{
+ int fd;
+ socks5_state_t state;
+ struct sockaddr_storage addr;
+
+ /* just check that the connection to the socks5 server works with
+ * the given credentials, and close without doing anything with it.
+ */
+
+ fd = socks5_socket(&state);
+ if (fd < 0) {
+ return -1;
+ }
+ if (socks5_connect(fd, server, port, &state) < 0) {
+ close(fd);
+ return -1;
+ }
+ while (state < SOCKS5_STATE_ESTABLISH) {
+ if (socks5_send(fd, user, password, addr, &state) < 0) {
+ close(fd);
+ return -1;
+ }
+ socks5_recv(fd, &state);
+ if (state == SOCKS5_STATE_NONE) {
+ close(fd);
+ return -1;
+ }
+ }
+ close(fd);
+
+ slirp->proxy_server = g_strdup(server);
+ slirp->proxy_port = port;
+ if (user) {
+ slirp->proxy_user = g_strdup(user);
+ }
+ if (password) {
+ slirp->proxy_password = g_strdup(password);
+ }
+
+ return 0;
+}
+
ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int flags)
{
if (so->s == -1 && so->extra) {
@@ -214,6 +214,12 @@ struct Slirp {
char *tftp_prefix;
struct tftp_session tftp_sessions[TFTP_SESSIONS_MAX];
+ /* proxy */
+ char *proxy_server;
+ int proxy_port;
+ char *proxy_user;
+ char *proxy_password;
+
ArpTable arp_table;
NdpTable ndp_table;
@@ -8,6 +8,8 @@
#ifndef SLIRP_SOCKET_H
#define SLIRP_SOCKET_H
+#include "socks5.h"
+
#define SO_EXPIRE 240000
#define SO_EXPIREFAST 10000
@@ -68,6 +70,8 @@ struct socket {
struct sbuf so_rcv; /* Receive buffer */
struct sbuf so_snd; /* Send buffer */
void * extra; /* Extra pointer */
+
+ socks5_state_t so_proxy_state;
};
new file mode 100644
@@ -0,0 +1,371 @@
+/* based on RFC 1928
+ * SOCKS Protocol Version 5
+ * based on RFC 1929
+ * Username/Password Authentication for SOCKS V5
+ * TODO:
+ * - RFC 1961 GSS-API Authentication Method for SOCKS Version 5
+ * - manage buffering on recv()
+ * - IPv6 connection to proxy
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/sockets.h"
+#include "qemu/log.h"
+
+#include "socks5.h"
+
+#define SOCKS_LEN_MAX UINT8_MAX
+
+#define SOCKS_VERSION_5 0x05
+
+#define SOCKS5_AUTH_METHOD_NONE 0x00
+#define SOCKS5_AUTH_METHOD_GSSAPI 0x01
+#define SOCKS5_AUTH_METHOD_PASSWORD 0x02
+#define SOCKS5_AUTH_METHOD_REJECTED 0xff
+
+#define SOCKS5_AUTH_PASSWORD_VERSION 0x01
+#define SOCKS5_AUTH_PASSWORD_SUCCESS 0x00
+
+#define SOCKS5_CMD_CONNECT 0x01
+#define SOCKS5_CMD_BIND 0x02
+#define SOCKS5_CMD_UDP_ASSOCIATE 0x03
+
+#define SOCKS5_ATYPE_IPV4 0x01
+#define SOCKS5_ATYPE_FQDN 0x03
+#define SOCKS5_ATYPE_IPV6 0x04
+
+#define SOCKS5_CMD_SUCCESS 0x00
+#define SOCKS5_CMD_SERVER_FAILURE 0x01
+#define SOCKS5_CMD_NOT_ALLOWED 0x02
+#define SOCKS5_CMD_NETWORK_UNREACHABLE 0x03
+#define SOCKS5_CMD_HOST_UNREACHABLE 0x04
+#define SOCKS5_CMD_CONNECTION_REFUSED 0x05
+#define SOCKS5_CMD_TTL_EXPIRED 0x06
+#define SOCKS5_CMD_NOT_SUPPORTED 0x07
+#define SOCKS5_CMD_ATYPE_NOT_SUPPORTED 0x08
+
+#define SOCKS5_NEGOCIATE_HDR_LEN 2
+#define SOCKS5_PASSWD_HDR_LEN 2
+#define SOCKS5_CONNECT_HDR_LEN 4
+#define SOCKS5_ATYPE_IPV4_LEN (sizeof(struct in_addr) + \
+ sizeof(in_port_t))
+#define SOCKS5_ATYPE_IPV6_LEN (sizeof(struct in6_addr) + \
+ sizeof(in_port_t))
+
+static int socks5_proxy_connect(int fd, const char *server, int port)
+{
+ struct sockaddr_in saddr;
+ struct hostent *he;
+
+ he = gethostbyname(server);
+ if (he == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+ /* TODO: IPv6 */
+ saddr.sin_family = AF_INET;
+ saddr.sin_addr = *(struct in_addr *)he->h_addr;
+ saddr.sin_port = htons(port);
+
+ return connect(fd, (struct sockaddr *)&saddr, sizeof(saddr));
+}
+
+static int socks5_send_negociate(int fd, const char *user,
+ const char *password)
+{
+ uint8_t cmd[4];
+ int len = 0;
+
+ cmd[len++] = SOCKS_VERSION_5;
+ if (user && password) {
+ cmd[len++] = 2;
+ cmd[len++] = SOCKS5_AUTH_METHOD_NONE;
+ cmd[len++] = SOCKS5_AUTH_METHOD_PASSWORD;
+ } else {
+ cmd[len++] = 1;
+ cmd[len++] = SOCKS5_AUTH_METHOD_NONE;
+ }
+ return send(fd, cmd, len, 0);
+}
+
+static int socks5_recv_negociate(int fd)
+{
+ char reply[2];
+
+ /* reply[0] is the protocol version number: 0x05
+ * reply[1] is the selected authentification protocol
+ */
+
+ if (recv(fd, reply, SOCKS5_NEGOCIATE_HDR_LEN, 0) !=
+ SOCKS5_NEGOCIATE_HDR_LEN) {
+ return -1;
+ }
+
+ if (reply[0] != SOCKS_VERSION_5) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ return (unsigned char)reply[1];
+}
+
+static int socks5_send_password(int fd, const char *user,
+ const char *password)
+{
+ uint8_t *cmd;
+ int len = 0, ulen, plen;
+
+ if (user == NULL || password == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ulen = strlen(user);
+ plen = strlen(password);
+ if (ulen > SOCKS_LEN_MAX || plen > SOCKS_LEN_MAX) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ cmd = alloca(3 + ulen + plen);
+
+ cmd[len++] = SOCKS5_AUTH_PASSWORD_VERSION;
+ cmd[len++] = ulen;
+ memcpy(cmd + len, user, ulen);
+ len += ulen;
+ cmd[len++] = plen;
+ memcpy(cmd + len, password, plen);
+
+ return send(fd, cmd, len, 0);
+}
+
+static int socks5_recv_password(int fd)
+{
+ char reply[2];
+ if (recv(fd, reply, SOCKS5_PASSWD_HDR_LEN, 0) != SOCKS5_PASSWD_HDR_LEN) {
+ return -1;
+ }
+
+ /* reply[0] is the subnegociation version number: 0x01
+ * reply[1] is the status
+ */
+ if (reply[0] != SOCKS5_AUTH_PASSWORD_VERSION ||
+ reply[1] != SOCKS5_AUTH_PASSWORD_SUCCESS) {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+static int socks5_send_connect(int fd, struct sockaddr_storage *addr)
+{
+ uint8_t cmd[22]; /* max size with IPv6 address */
+ int len = 0;
+
+ cmd[len++] = SOCKS_VERSION_5;
+ cmd[len++] = SOCKS5_CMD_CONNECT;
+ cmd[len++] = 0; /* reserved */
+
+ switch (addr->ss_family) {
+ case AF_INET: {
+ struct sockaddr_in *a = (struct sockaddr_in *)addr;
+ cmd[len++] = SOCKS5_ATYPE_IPV4;
+ memcpy(cmd + len, &a->sin_addr, sizeof(struct in_addr));
+ len += sizeof(struct in_addr);
+ memcpy(cmd + len, &a->sin_port, sizeof(in_port_t));
+ len += sizeof(in_port_t);
+ }
+ break;
+ case AF_INET6: {
+ struct sockaddr_in6 *a = (struct sockaddr_in6 *)addr;
+ cmd[len++] = SOCKS5_ATYPE_IPV6;
+ memcpy(cmd + len, &a->sin6_addr, sizeof(struct in6_addr));
+ len += sizeof(struct in6_addr);
+ memcpy(cmd + len, &a->sin6_port, sizeof(in_port_t));
+ len += sizeof(in_port_t);
+ }
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ return send(fd, cmd, len, 0);
+}
+
+static int socks5_recv_connect(int fd)
+{
+ uint8_t reply[7 + SOCKS_LEN_MAX]; /* can contains a FQDN */
+
+ /*
+ * reply[0] is protocol version: 5
+ * reply[1] is reply field
+ * reply[2] is reserved
+ * reply[3] is address type */
+
+ if (recv(fd, reply, SOCKS5_CONNECT_HDR_LEN, 0) != SOCKS5_CONNECT_HDR_LEN) {
+ return -1;
+ }
+
+ if (reply[0] != SOCKS_VERSION_5) {
+ qemu_log_mask(LOG_GUEST_ERROR, "Invalid SOCKS version: %d\n", reply[0]);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (reply[1] != SOCKS5_CMD_SUCCESS) {
+ qemu_log_mask(LOG_GUEST_ERROR, "SOCKS5 connection error: %d\n",
+ reply[1]);
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (reply[3]) {
+ case SOCKS5_ATYPE_IPV4:
+ if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN,
+ SOCKS5_ATYPE_IPV4_LEN, 0) != SOCKS5_ATYPE_IPV4_LEN) {
+ return -1;
+ }
+ break;
+ case SOCKS5_ATYPE_IPV6:
+ if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN,
+ SOCKS5_ATYPE_IPV6_LEN, 0) != SOCKS5_ATYPE_IPV6_LEN) {
+ return -1;
+ }
+ break;
+ case SOCKS5_ATYPE_FQDN:
+ if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN, 1, 0) != 1) {
+ return -1;
+ }
+ if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN + 1,
+ reply[SOCKS5_CONNECT_HDR_LEN], 0) !=
+ reply[SOCKS5_CONNECT_HDR_LEN]) {
+ return -1;
+ }
+ qemu_log_mask(LOG_GUEST_ERROR, "Unsupported SOCKS5 ATYPE: FDDN\n");
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+int socks5_socket(socks5_state_t *state)
+{
+ *state = SOCKS5_STATE_CONNECT;
+ return qemu_socket(AF_INET, SOCK_STREAM, 0);
+}
+
+int socks5_connect(int fd, const char *server, int port,
+ socks5_state_t *state)
+{
+ if (*state != SOCKS5_STATE_CONNECT) {
+ *state = SOCKS5_STATE_NONE;
+ errno = EINVAL;
+ return -1;
+ }
+
+ *state = SOCKS5_STATE_NEGOCIATE;
+ return socks5_proxy_connect(fd, server, port);
+}
+
+int socks5_send(int fd, const char *user, const char *password,
+ struct sockaddr_storage addr, socks5_state_t *state)
+{
+ int ret;
+
+ switch (*state) {
+ case SOCKS5_STATE_NEGOCIATE:
+ ret = socks5_send_negociate(fd, user, password);
+ if (ret < 0) {
+ return ret;
+ }
+ *state = SOCKS5_STATE_NEGOCIATING;
+ break;
+ case SOCKS5_STATE_AUTHENTICATE:
+ ret = socks5_send_password(fd, user, password);
+ if (ret < 0) {
+ return ret;
+ }
+ *state = SOCKS5_STATE_AUTHENTICATING;
+ break;
+ case SOCKS5_STATE_ESTABLISH:
+ ret = socks5_send_connect(fd, &addr);
+ if (ret < 0) {
+ return ret;
+ }
+ *state = SOCKS5_STATE_ESTABLISHING;
+ break;
+ case SOCKS5_STATE_NONE:
+ return 1;
+ case SOCKS5_STATE_NEGOCIATING:
+ case SOCKS5_STATE_AUTHENTICATING:
+ case SOCKS5_STATE_ESTABLISHING:
+ return 0;
+ case SOCKS5_STATE_CONNECT:
+ case SOCKS5_STATE_ERROR:
+ *state = SOCKS5_STATE_ERROR;
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+void socks5_recv(int fd, socks5_state_t *state)
+{
+ int ret;
+
+ switch (*state) {
+ case SOCKS5_STATE_NEGOCIATING:
+ ret = socks5_recv_negociate(fd);
+ if (ret < 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SOCKS5 AUTH method error: %d\n", errno);
+ *state = SOCKS5_STATE_ERROR;
+ return;
+ }
+ switch (ret) {
+ case SOCKS5_AUTH_METHOD_NONE: /* no authentification */
+ *state = SOCKS5_STATE_ESTABLISH;
+ break;
+ case SOCKS5_AUTH_METHOD_PASSWORD: /* username/password */
+ *state = SOCKS5_STATE_AUTHENTICATE;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "SOCKS5 unsupported AUTH method: %d\n", ret);
+ *state = SOCKS5_STATE_ERROR;
+ break;
+ }
+ break;
+ case SOCKS5_STATE_AUTHENTICATING:
+ ret = socks5_recv_password(fd);
+ if (ret < 0) {
+ *state = SOCKS5_STATE_ERROR;
+ return;
+ }
+ *state = SOCKS5_STATE_ESTABLISH;
+ break;
+ case SOCKS5_STATE_ESTABLISHING:
+ ret = socks5_recv_connect(fd);
+ if (ret < 0) {
+ *state = SOCKS5_STATE_ERROR;
+ return;
+ }
+ *state = SOCKS5_STATE_NONE;
+ break;
+ case SOCKS5_STATE_NONE:
+ case SOCKS5_STATE_CONNECT:
+ case SOCKS5_STATE_NEGOCIATE:
+ case SOCKS5_STATE_AUTHENTICATE:
+ case SOCKS5_STATE_ESTABLISH:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Internal error: invalid state in socks5_recv(): %d\n",
+ *state);
+ *state = SOCKS5_STATE_ERROR;
+ break;
+ case SOCKS5_STATE_ERROR:
+ break;
+ }
+}
new file mode 100644
@@ -0,0 +1,25 @@
+#ifndef SOCKS5_H
+#define SOCKS5_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+typedef enum {
+ SOCKS5_STATE_NONE = 0,
+ SOCKS5_STATE_CONNECT,
+ SOCKS5_STATE_NEGOCIATE,
+ SOCKS5_STATE_NEGOCIATING,
+ SOCKS5_STATE_AUTHENTICATE,
+ SOCKS5_STATE_AUTHENTICATING,
+ SOCKS5_STATE_ESTABLISH,
+ SOCKS5_STATE_ESTABLISHING,
+ SOCKS5_STATE_ERROR,
+} socks5_state_t;
+
+int socks5_socket(socks5_state_t *state);
+int socks5_connect(int fd, const char *server, int port,
+ socks5_state_t *state);
+int socks5_send(int fd, const char *user, const char *password,
+ struct sockaddr_storage addr, socks5_state_t *state);
+void socks5_recv(int fd, socks5_state_t *state);
+#endif
@@ -40,6 +40,7 @@
#include "qemu/osdep.h"
#include "slirp.h"
+#include "socks5.h"
/* patchable/settable parameters for tcp */
/* Don't do rfc1323 performance enhancements */
@@ -394,11 +395,22 @@ tcp_sockclosed(struct tcpcb *tp)
int tcp_fconnect(struct socket *so, unsigned short af)
{
int ret=0;
+ Slirp *slirp = so->slirp;
DEBUG_CALL("tcp_fconnect");
DEBUG_ARG("so = %p", so);
- ret = so->s = qemu_socket(af, SOCK_STREAM, 0);
+ /* pass all non-local traffic through the proxy */
+ if (slirp->proxy_server &&
+ !(af == AF_INET &&
+ (so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) ==
+ slirp->vnetwork_addr.s_addr) &&
+ !(af == AF_INET6 && in6_equal_host(&so->so_faddr6))) {
+ ret = so->s = socks5_socket(&so->so_proxy_state);
+ } else {
+ ret = so->s = qemu_socket(af, SOCK_STREAM, 0);
+ }
+
if (ret >= 0) {
int opt, s=so->s;
struct sockaddr_storage addr;
@@ -413,8 +425,12 @@ int tcp_fconnect(struct socket *so, unsigned short af)
sotranslate_out(so, &addr);
/* We don't care what port we get */
- ret = connect(s, (struct sockaddr *)&addr, sockaddr_size(&addr));
-
+ if (so->so_proxy_state) {
+ ret = socks5_connect(s, slirp->proxy_server, slirp->proxy_port,
+ &so->so_proxy_state);
+ } else {
+ ret = connect(s, (struct sockaddr *)&addr, sockaddr_size(&addr));
+ }
/*
* If it's not in progress, it failed, so we just return 0,
* without clearing SS_NOFDREF
@@ -160,6 +160,15 @@ udp_input(register struct mbuf *m, int iphlen)
goto bad;
}
+ /* as our SOCKS5 client is not able to route UDP packets,
+ * only allow local UDP traffic (we need it for DNS)
+ */
+ if (slirp->proxy_server &&
+ (ip->ip_dst.s_addr & slirp->vnetwork_mask.s_addr) !=
+ slirp->vnetwork_addr.s_addr) {
+ goto bad;
+ }
+
/*
* Locate pcb for datagram.
*/
@@ -27,6 +27,14 @@ void udp6_input(struct mbuf *m)
}
ip = mtod(m, struct ip6 *);
+
+ /* as our SOCKS5 client is not able to route UDP6 packets,
+ * only allow local UDP6 traffic
+ */
+ if (slirp->proxy_server && !in6_equal_host(&ip->ip_dst)) {
+ goto bad;
+ }
+
m->m_len -= iphlen;
m->m_data += iphlen;
uh = mtod(m, struct udphdr *);
When the VM is used behind a firewall, This allows the use of a SOCKS5 proxy server to connect the VM IP stack directly to the Internet. This implementation doesn't manage UDP packets, so they are simply dropped (as with restrict=on), except for the localhost as we need it for DNS. Signed-off-by: Laurent Vivier <laurent@vivier.eu> --- net/slirp.c | 39 +++++- qapi-schema.json | 9 ++ qemu-options.hx | 11 ++ slirp/Makefile.objs | 2 +- slirp/ip_icmp.c | 2 +- slirp/libslirp.h | 3 + slirp/slirp.c | 69 +++++++++- slirp/slirp.h | 6 + slirp/socket.h | 4 + slirp/socks5.c | 371 ++++++++++++++++++++++++++++++++++++++++++++++++++++ slirp/socks5.h | 25 ++++ slirp/tcp_subr.c | 22 +++- slirp/udp.c | 9 ++ slirp/udp6.c | 8 ++ 14 files changed, 569 insertions(+), 11 deletions(-) create mode 100644 slirp/socks5.c create mode 100644 slirp/socks5.h