qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v5 1/1] slirp: add SOCKS5 support


From: Laurent Vivier
Subject: [Qemu-devel] [PATCH v5 1/1] slirp: add SOCKS5 support
Date: Tue, 9 May 2017 21:31:12 +0200

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 <address@hidden>
---
 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       |  65 +++++++++
 slirp/slirp.h       |   6 +
 slirp/socket.h      |   4 +
 slirp/socks5.c      | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 slirp/socks5.h      |  31 +++++
 slirp/tcp_subr.c    |  22 ++-
 slirp/udp.c         |   9 ++
 slirp/udp6.c        |   8 ++
 14 files changed, 583 insertions(+), 7 deletions(-)
 create mode 100644 slirp/socks5.c
 create mode 100644 slirp/socks5.h

diff --git a/net/slirp.c b/net/slirp.c
index c705a60..06a32f7 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -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;
diff --git a/qapi-schema.json b/qapi-schema.json
index 01b087f..bcaf85b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -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'] } }
 
diff --git a/qemu-options.hx b/qemu-options.hx
index f68829f..0a26a62 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -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.
 
address@hidden address@hidden:@var{port}[,address@hidden,address@hidden
+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:
address@hidden
+qemu-system-i386 -net user,proxy-server=localhost:9050
address@hidden example
+
 @item hostfwd=[tcp|udp]:address@hidden:@address@hidden:@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
diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs
index 28049b0..3cf6c8d 100644
--- a/slirp/Makefile.objs
+++ b/slirp/Makefile.objs
@@ -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
diff --git a/slirp/ip_icmp.c b/slirp/ip_icmp.c
index 0b667a4..748e18c 100644
--- a/slirp/ip_icmp.c
+++ b/slirp/ip_icmp.c
@@ -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;
diff --git a/slirp/libslirp.h b/slirp/libslirp.h
index f90f0f5..e6fc3f3 100644
--- a/slirp/libslirp.h
+++ b/slirp/libslirp.h
@@ -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);
 
diff --git a/slirp/slirp.c b/slirp/slirp.c
index 2f2ec2c..bd7d736 100644
--- a/slirp/slirp.c
+++ b/slirp/slirp.c
@@ -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,11 @@ 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_proxy_state &&
+                        so->so_state & SS_ISFCONNECTING) {
+                        socks5_recv(so->s, &so->so_proxy_state);
+                        continue;
+                    }
                     /*
                      * Check for incoming connections
                      */
@@ -645,12 +655,23 @@ void slirp_pollfds_poll(GArray *pollfds, int select_error)
                     /*
                      * Check for non-blocking, still-connecting sockets
                      */
+
                     if (so->so_state & SS_ISFCONNECTING) {
+                        if (so->so_proxy_state) {
+                            ret = socks5_send(so->s, slirp->proxy_user,
+                                              slirp->proxy_password, 
so->fhost.ss,
+                                              &so->so_proxy_state);
+                            if (ret < 0) {
+                                goto write_error;
+                            }
+                            continue;
+                        }
                         /* Connected */
                         so->so_state &= ~SS_ISFCONNECTING;
 
                         ret = send(so->s, (const void *) &ret, 0, 0);
                         if (ret < 0) {
+write_error:
                             /* XXXXX Must fix, zero bytes is a NOP */
                             if (errno == EAGAIN || errno == EWOULDBLOCK ||
                                 errno == EINPROGRESS || errno == ENOTCONN) {
@@ -1073,6 +1094,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_ERROR) {
+            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) {
diff --git a/slirp/slirp.h b/slirp/slirp.h
index 5af4f48..d4ee236 100644
--- a/slirp/slirp.h
+++ b/slirp/slirp.h
@@ -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;
 
diff --git a/slirp/socket.h b/slirp/socket.h
index 2f224bc..6d9976b 100644
--- a/slirp/socket.h
+++ b/slirp/socket.h
@@ -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;
 };
 
 
diff --git a/slirp/socks5.c b/slirp/socks5.c
new file mode 100644
index 0000000..6a2bc9a
--- /dev/null
+++ b/slirp/socks5.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2017, Laurent Vivier <address@hidden>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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;
+    }
+}
diff --git a/slirp/socks5.h b/slirp/socks5.h
new file mode 100644
index 0000000..3f16554
--- /dev/null
+++ b/slirp/socks5.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017, Laurent Vivier <address@hidden>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef SOCKS5_H
+#define SOCKS5_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
diff --git a/slirp/tcp_subr.c b/slirp/tcp_subr.c
index ed16e18..14fde73 100644
--- a/slirp/tcp_subr.c
+++ b/slirp/tcp_subr.c
@@ -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
diff --git a/slirp/udp.c b/slirp/udp.c
index 227d779..1f4b39c 100644
--- a/slirp/udp.c
+++ b/slirp/udp.c
@@ -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.
         */
diff --git a/slirp/udp6.c b/slirp/udp6.c
index 9fa314b..173e930 100644
--- a/slirp/udp6.c
+++ b/slirp/udp6.c
@@ -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 *);
-- 
2.9.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]