diff mbox

[v4,1/1] slirp: add SOCKS5 support

Message ID 20170505224833.17769-2-laurent@vivier.eu
State New
Headers show

Commit Message

Laurent Vivier May 5, 2017, 10:48 p.m. UTC
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

Comments

Samuel Thibault May 5, 2017, 11:27 p.m. UTC | #1
Hello,

Laurent Vivier, on sam. 06 mai 2017 00:48:33 +0200, wrote:
> @@ -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;
> +                    }

Again, I don't see how this can work with both socks5 case and
non-socks5 case.  Don't we need to somehow check for the type of socket
before calling socks5_recv?

> @@ -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,

Ditto.

> diff --git a/slirp/socks5.c b/slirp/socks5.c
> new file mode 100644
> index 0000000..2bba045
> --- /dev/null
> +++ b/slirp/socks5.c
> @@ -0,0 +1,371 @@

In v2 of the patch, this was said to have "some parts from nmap/ncat
GPLv2".  Is that really not true any more?  If any part of the file is
not original, it *has* to wear proper copyright notices, otherwise it's
copyright infrigement.

Also, see the bot build error report: <sys/socket.h> doesn't exist on
windows,

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>

should be used instead.

Samuel
Laurent Vivier May 6, 2017, 1:19 p.m. UTC | #2
Le 06/05/2017 à 01:27, Samuel Thibault a écrit :
> Hello,
> 

Hi,

> Laurent Vivier, on sam. 06 mai 2017 00:48:33 +0200, wrote:
>> @@ -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;
>> +                    }
> 
> Again, I don't see how this can work with both socks5 case and
> non-socks5 case.  Don't we need to somehow check for the type of socket
> before calling socks5_recv?

The check is done previously by:

@@ -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;
+                }

We can enter in socks5_recv() only if so->so_proxy_state is in a valid
state because G_IO_IN triggers that.

> 
>> @@ -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,
> 
> Ditto.

if so_proxy_state is 0 the function does nothing and all remains as
without the function call (it's the case "ret > 0)"

> 
>> diff --git a/slirp/socks5.c b/slirp/socks5.c
>> new file mode 100644
>> index 0000000..2bba045
>> --- /dev/null
>> +++ b/slirp/socks5.c
>> @@ -0,0 +1,371 @@
> 
> In v2 of the patch, this was said to have "some parts from nmap/ncat
> GPLv2".  Is that really not true any more?  If any part of the file is
> not original, it *has* to wear proper copyright notices, otherwise it's
> copyright infrigement.

No, I've re-written entirely this part from RFC.

for ncat.h license is 281 lines long (with clarifications and
extensions) for 60 lines copied...

> Also, see the bot build error report: <sys/socket.h> doesn't exist on
> windows,
> 
> #include <windows.h>
> #include <winsock2.h>
> #include <ws2tcpip.h>
> 
> should be used instead.

In fact, the include is not needed at all.

Thanks,
Laurent
Samuel Thibault May 8, 2017, 8:09 p.m. UTC | #3
Hello,

Laurent Vivier, on sam. 06 mai 2017 15:19:44 +0200, wrote:
> > Laurent Vivier, on sam. 06 mai 2017 00:48:33 +0200, wrote:
> >> @@ -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;
> >> +                    }
> > 
> > Again, I don't see how this can work with both socks5 case and
> > non-socks5 case.  Don't we need to somehow check for the type of socket
> > before calling socks5_recv?
> 
> The check is done previously by:
> 
> @@ -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;
> +                }
> 
> We can enter in socks5_recv() only if so->so_proxy_state is in a valid
> state because G_IO_IN triggers that.

I don't understand: the socks5_recv gets called not only when pfd.events
contains G_IO_IN, but also when it contains G_IO_HUP or G_IO_ERR.  Also,
so_proxy_state being non-nul is not the only way to have G_IO_IN set in
pfd.events, so_state & SS_FACCEPTCONN and CONN_CANFRCV(so) also trigger
that.

> >> @@ -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,
> > 
> > Ditto.
> 
> if so_proxy_state is 0 the function does nothing

Well, that's quite weak reason to me, and will confuse readers, because
they'll think that the code is for the socks5 case only.

> >> +++ b/slirp/socks5.c
> >> @@ -0,0 +1,371 @@
> > 
> > In v2 of the patch, this was said to have "some parts from nmap/ncat
> > GPLv2".  Is that really not true any more?  If any part of the file is
> > not original, it *has* to wear proper copyright notices, otherwise it's
> > copyright infrigement.
> 
> No, I've re-written entirely this part from RFC.

Ok.

> for ncat.h

Which code comes from ncat.h?

Again, we can't copy/paste code like that, that is copyright
infrigement.

> license is 281 lines long (with clarifications and
> extensions) for 60 lines copied...

That is really no reason.  Copyright thingies are considered as soon
as dozen-ish lines of non-trivial code.  The size of the terms of the
licence itself really doesn't matter.  If we don't respect others'
copyright, we can't expect others (e.g. companies) to respect our GPL
code.

Samuel
Laurent Vivier May 9, 2017, 7:21 a.m. UTC | #4
Le 08/05/2017 à 22:09, Samuel Thibault a écrit :
> Hello,
> 
> Laurent Vivier, on sam. 06 mai 2017 15:19:44 +0200, wrote:
>>> Laurent Vivier, on sam. 06 mai 2017 00:48:33 +0200, wrote:
>>>> @@ -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;
>>>> +                    }
>>>
>>> Again, I don't see how this can work with both socks5 case and
>>> non-socks5 case.  Don't we need to somehow check for the type of socket
>>> before calling socks5_recv?
>>
>> The check is done previously by:
>>
>> @@ -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;
>> +                }
>>
>> We can enter in socks5_recv() only if so->so_proxy_state is in a valid
>> state because G_IO_IN triggers that.
> 
> I don't understand: the socks5_recv gets called not only when pfd.events
> contains G_IO_IN, but also when it contains G_IO_HUP or G_IO_ERR.  Also,
> so_proxy_state being non-nul is not the only way to have G_IO_IN set in
> pfd.events, so_state & SS_FACCEPTCONN and CONN_CANFRCV(so) also trigger
> that.

This is filtered by (so_state & SS_ISFCONNECTING). The only case when we
enter in the receiving part with SS_ISFCONNECTING is when socks5 part
has been enabled.

>>>> @@ -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,
>>>
>>> Ditto.
>>
>> if so_proxy_state is 0 the function does nothing
> 
> Well, that's quite weak reason to me, and will confuse readers, because
> they'll think that the code is for the socks5 case only.

OK, I'm going to add a "if (so->so_proxy_state && so->so_proxy_state !=
SOCKS5_STATE_ERROR)" here.

> 
>>>> +++ b/slirp/socks5.c
>>>> @@ -0,0 +1,371 @@
>>>
>>> In v2 of the patch, this was said to have "some parts from nmap/ncat
>>> GPLv2".  Is that really not true any more?  If any part of the file is
>>> not original, it *has* to wear proper copyright notices, otherwise it's
>>> copyright infrigement.
>>
>> No, I've re-written entirely this part from RFC.
> 
> Ok.
> 
>> for ncat.h
> 
> Which code comes from ncat.h?

I haven't been clear: none. I've entirely re-written this part.

> Again, we can't copy/paste code like that, that is copyright
> infrigement.
> 
>> license is 281 lines long (with clarifications and
>> extensions) for 60 lines copied...
> 
> That is really no reason.  Copyright thingies are considered as soon
> as dozen-ish lines of non-trivial code.  The size of the terms of the
> licence itself really doesn't matter.  If we don't respect others'
> copyright, we can't expect others (e.g. companies) to respect our GPL
> code.

I respect others copyright. There is no more nmap/ncat code in this patch.

Thanks,
Laurent
Samuel Thibault May 9, 2017, 7:21 p.m. UTC | #5
Hello,

Laurent Vivier, on mar. 09 mai 2017 09:21:09 +0200, wrote:
> Le 08/05/2017 à 22:09, Samuel Thibault a écrit :
> > Laurent Vivier, on sam. 06 mai 2017 15:19:44 +0200, wrote:
> >>> Laurent Vivier, on sam. 06 mai 2017 00:48:33 +0200, wrote:
> >> The check is done previously by:
> >>
> >> @@ -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;
> >> +                }
> >>
> >> We can enter in socks5_recv() only if so->so_proxy_state is in a valid
> >> state because G_IO_IN triggers that.
> > 
> > I don't understand: the socks5_recv gets called not only when pfd.events
> > contains G_IO_IN, but also when it contains G_IO_HUP or G_IO_ERR.  Also,
> > so_proxy_state being non-nul is not the only way to have G_IO_IN set in
> > pfd.events, so_state & SS_FACCEPTCONN and CONN_CANFRCV(so) also trigger
> > that.
> 
> This is filtered by (so_state & SS_ISFCONNECTING). The only case when we
> enter in the receiving part with SS_ISFCONNECTING is when socks5 part
> has been enabled.

The code snippet above is guarded by (so_state & SS_ISFCONNECTING),
but that won't prevent socks5_recv from being called here in
slirp_pollfds_poll in the non-socks5 case:

                 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;
+                    }
                     /*

e.g. when (so->so_state & SS_FACCEPTCONN) or when CONN_CANFRCV(so) in
slirp_pollfds_fill, which both set G_IO_IN too.

> >>>> @@ -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,
> >>>
> >>> Ditto.
> >>
> >> if so_proxy_state is 0 the function does nothing
> > 
> > Well, that's quite weak reason to me, and will confuse readers, because
> > they'll think that the code is for the socks5 case only.
> 
> OK, I'm going to add a "if (so->so_proxy_state && so->so_proxy_state !=
> SOCKS5_STATE_ERROR)" here.

Thanks :)

> >>>> +++ b/slirp/socks5.c
> >>>> @@ -0,0 +1,371 @@
> >>>
> >>> In v2 of the patch, this was said to have "some parts from nmap/ncat
> >>> GPLv2".  Is that really not true any more?  If any part of the file is
> >>> not original, it *has* to wear proper copyright notices, otherwise it's
> >>> copyright infrigement.
> >>
> >> No, I've re-written entirely this part from RFC.
> > 
> > Ok.
> > 
> >> for ncat.h
> > 
> > Which code comes from ncat.h?
> 
> I haven't been clear: none. I've entirely re-written this part.

Ah, ok.

Samuel
diff mbox

Patch

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.
 
+@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
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..859581b 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,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) {
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..2bba045
--- /dev/null
+++ b/slirp/socks5.c
@@ -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;
+    }
+}
diff --git a/slirp/socks5.h b/slirp/socks5.h
new file mode 100644
index 0000000..5f2ea8e
--- /dev/null
+++ b/slirp/socks5.h
@@ -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
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 *);