From nobody Fri May 3 01:25:55 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1491263888801889.6853387514257; Mon, 3 Apr 2017 16:58:08 -0700 (PDT) Received: from localhost ([::1]:33358 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cvBrL-0008NT-Ke for importer@patchew.org; Mon, 03 Apr 2017 19:58:07 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:40580) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cvBq9-0007kQ-H6 for qemu-devel@nongnu.org; Mon, 03 Apr 2017 19:56:56 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cvBq4-0002Sd-R5 for qemu-devel@nongnu.org; Mon, 03 Apr 2017 19:56:53 -0400 Received: from mout.kundenserver.de ([212.227.17.24]:65443) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1cvBq4-0002Rf-9w for qemu-devel@nongnu.org; Mon, 03 Apr 2017 19:56:48 -0400 Received: from localhost.localdomain ([78.238.229.36]) by mrelayeu.kundenserver.de (mreue103 [212.227.15.183]) with ESMTPSA (Nemesis) id 0MCOnp-1cmd782ZqD-009D0R; Tue, 04 Apr 2017 01:56:40 +0200 From: Laurent Vivier To: Samuel Thibault Date: Tue, 4 Apr 2017 01:56:36 +0200 Message-Id: <20170403235636.5647-2-laurent@vivier.eu> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170403235636.5647-1-laurent@vivier.eu> References: <20170403235636.5647-1-laurent@vivier.eu> X-Provags-ID: V03:K0:mf8v3I9DOMrA2tetB6esPwvbM7kLRXArtIFrelT/nd+0jm0X18e KvZa0uOy1Orjlnl64GGq1ULDDUyqtlgwS4H/7uzbve2WqCDAwAyzNt3QzB9GMxvYwAA0Grc AhFVXV1atfl9opztaHfudZ3yvyRVu/uG/UOMtTpHWpQDrVTODbFiwzlRavtdoQk4GXvmfau yjheSY8/hav4wjNPrhnuA== X-UI-Out-Filterresults: notjunk:1;V01:K0:uFGGWGVxAvU=:gJJX9NXxgmG+A6YM97/Cd7 sGo/7VJMfSvGZ5hBMqoG8vUrqFhEeAoTCnleAlFFHTRIhDP0Sg0afPsu4Eb0KtEVBRTEilFCC QWGQA0l9INdcviAPtnfMl++OjAtYjFcDfIacX/3iSzpwsBb+tK1gdNnOg6HxxLK6IR6QGcS3x WokNDQa87/l2MZdjKnRbgtZCtle0jOKODH4kBZmfxZwBTLjCdUBxePwwWYkivYvcsUyn2oZo1 yBuIoY40pDeG94feTUkG76pWrkGvA0mosyBFPYH2kWiibQp6R/aLTXLfk6O7F6jvKVLXJpgAQ yBB3UJgjH6o2NZBlYvZF/VmDI0vV8f7wVDRNBS00jfObrMPA6HlF2DJBQgjtS5ZtTiemYSxPe bMV0VDx6ddCDRP2fs0xX6YFkS7pCOewC1OOKJKu74MaBXWEcbGQ6Yx2F8YQkZZY4qr9xTT2do q8rZuEB7QXPhlE8tzW6WddBSEuVaYD1l8tJIyhJeHOfr/1ctUAjZrKT19StHe7mg0m87Y/M/N wswI+A/zAhwezNecJ+PgcHqdbGLKt8dvn0NZjB1Gv0EC+QJior3hNXyLuuK88E6XF05WOtD8p 9iODTtCNJScmXYzIL4zWAFksRbirrm13YIMIGqPSrIwKEzqkasMTdGidNHxfXzW2ZiW1drLUm LoDoNjw1Nz91AqnW6p6wm0Mq64d3agjNhJkAoJ0EEEWbx32P4qEr5WXmDtLOWu1wYhGY= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.17.24 Subject: [Qemu-devel] [PATCH v3 1/1] slirp: add SOCKS5 support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Jason Wang , qemu-devel@nongnu.org, Laurent Vivier Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" 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=3Don), except for the localhost as we need it for DNS. Signed-off-by: Laurent Vivier --- 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 | 68 ++++++++++- slirp/slirp.h | 6 + slirp/socket.h | 4 + slirp/socks5.c | 328 ++++++++++++++++++++++++++++++++++++++++++++++++= ++++ slirp/socks5.h | 24 ++++ slirp/tcp_subr.c | 22 +++- slirp/udp.c | 9 ++ slirp/udp6.c | 8 ++ 14 files changed, 524 insertions(+), 11 deletions(-) create mode 100644 slirp/socks5.c create mode 100644 slirp/socks5.h diff --git a/net/slirp.c b/net/slirp.c index f97ec23..8a5dc3f 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" =20 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); } =20 +static int net_slirp_add_proxy(SlirpState *s, const char *proxy_server, + const char *proxy_user, + const char *proxy_secretid) +{ + InetSocketAddress *addr; + char *password =3D NULL; + int ret; + + if (proxy_server =3D=3D NULL) { + return 0; + } + + if (proxy_secretid) { + password =3D qcrypto_secret_lookup_as_utf8(proxy_secretid, &error_= fatal); + } + + addr =3D inet_parse(proxy_server, &error_fatal); + + ret =3D 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 =3D { .type =3D NET_CLIENT_DRIVER_USER, .size =3D sizeof(SlirpState), @@ -155,7 +183,8 @@ static int net_slirp_init(NetClientState *peer, const c= har *model, const char *bootfile, const char *vdhcp_start, const char *vnameserver, const char *vnameserver= 6, const char *smb_export, const char *vsmbserver, - const char **dnssearch) + const char **dnssearch, const char *proxy_server, + const char *proxy_user, const char *proxy_secret= id) { /* default settings according to historic slirp */ struct in_addr net =3D { .s_addr =3D htonl(0x0a000200) }; /* 10.0.2.0= */ @@ -361,6 +390,11 @@ static int net_slirp_init(NetClientState *peer, const = char *model, } #endif =20 + if (net_slirp_add_proxy(s, proxy_server, + proxy_user, proxy_secretid) < 0) { + goto error; + } + s->exit_notifier.notify =3D slirp_smb_exit; qemu_add_exit_notifier(&s->exit_notifier); return 0; @@ -878,7 +912,8 @@ int net_init_slirp(const Netdev *netdev, const char *na= me, 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); =20 while (slirp_configs) { config =3D slirp_configs; diff --git a/qapi-schema.json b/qapi-schema.json index b921994..1799ae2 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3658,6 +3658,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', @@ -3680,6 +3686,9 @@ '*ipv6-dns': 'str', '*smb': 'str', '*smbserver': 'str', + '*proxy-server': 'str', + '*proxy-user': 'str', + '*proxy-secretid': 'str', '*hostfwd': ['String'], '*guestfwd': ['String'] } } =20 diff --git a/qemu-options.hx b/qemu-options.hx index 99af8ed..e625d1a 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1645,6 +1645,7 @@ DEF("netdev", HAS_ARG, QEMU_OPTION_netdev, #ifndef _WIN32 "[,smb=3Ddir[,smbserver=3Dadd= r]]\n" #endif + " [,proxy-server=3Daddr:port[,proxy-user=3Duser,proxy-secretid= =3Did]]\n" " configure a user mode network backend with ID 'str',\= n" " its DHCP server and optional services\n" #endif @@ -1883,6 +1884,16 @@ Note that a SAMBA server must be installed on the ho= st OS. QEMU was tested successfully with smbd versions from Red Hat 9, Fedora Core 3 and OpenSUSE 11.x. =20 +@item proxy-server=3D@var{addr}:@var{port}[,proxy-user=3D@var{user},proxy-= secretid=3D@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 obj= ect). + +For example, to connect to a TOR proxy server on the host, use the followi= ng: +@example +qemu-system-i386 -net user,proxy-server=3Dlocalhost:9050 +@end example + @item hostfwd=3D[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 1baa1f1..ce6d643 100644 --- a/slirp/Makefile.objs +++ b/slirp/Makefile.objs @@ -2,4 +2,4 @@ common-obj-y =3D cksum.o if.o ip_icmp.o ip6_icmp.o ip6_inpu= t.o ip6_output.o \ ip_input.o ip_output.o dnssearch.o dhcpv6.o common-obj-y +=3D slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_ou= tput.o common-obj-y +=3D tcp_subr.o tcp_timer.o udp.o udp6.o bootp.o tftp.o arp_t= able.o \ - ndp_table.o + ndp_table.o socks5.o diff --git a/slirp/ip_icmp.c b/slirp/ip_icmp.c index 5ffc7a6..ed5e3eb 100644 --- a/slirp/ip_icmp.c +++ b/slirp/ip_icmp.c @@ -154,7 +154,7 @@ icmp_input(struct mbuf *m, int hlen) ip->ip_len +=3D hlen; /* since ip_input subtracts this */ if (ip->ip_dst.s_addr =3D=3D slirp->vhost_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= ); =20 void slirp_input(Slirp *slirp, const uint8_t *pkt, int pkt_len); =20 +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); =20 diff --git a/slirp/slirp.c b/slirp/slirp.c index 5a94b06..529bf22 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" =20 #ifndef _WIN32 #include @@ -442,6 +443,9 @@ void slirp_pollfds_fill(GArray *pollfds, uint32_t *time= out) .fd =3D so->s, .events =3D G_IO_OUT | G_IO_ERR, }; + if (so->so_proxy_state) { + pfd.events |=3D G_IO_IN; + } so->pollfds_idx =3D pollfds->len; g_array_append_val(pollfds, pfd); continue; @@ -617,6 +621,10 @@ void slirp_pollfds_poll(GArray *pollfds, int select_er= ror) * 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 +653,19 @@ void slirp_pollfds_poll(GArray *pollfds, int select_e= rror) /* * Check for non-blocking, still-connecting sockets */ - if (so->so_state & SS_ISFCONNECTING) { - /* Connected */ - so->so_state &=3D ~SS_ISFCONNECTING; =20 - ret =3D send(so->s, (const void *) &ret, 0, 0); + if (so->so_state & SS_ISFCONNECTING) { + ret =3D socks5_send(so->s, slirp->proxy_user, + slirp->proxy_password, so->fhost= .ss, + &so->so_proxy_state); + if (ret =3D=3D 0) { + continue; + } + if (ret > 0) { + /* Connected */ + so->so_state &=3D ~SS_ISFCONNECTING; + ret =3D send(so->s, (const void *) &ret, 0, 0); + } if (ret < 0) { /* XXXXX Must fix, zero bytes is a NOP */ if (errno =3D=3D EAGAIN || errno =3D=3D EWOULD= BLOCK || @@ -1069,6 +1085,50 @@ int slirp_add_exec(Slirp *slirp, int do_pty, const v= oid *args, htons(guest_port)); } =20 +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 =3D 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 =3D=3D SOCKS5_STATE_NONE) { + close(fd); + return -1; + } + } + close(fd); + + slirp->proxy_server =3D g_strdup(server); + slirp->proxy_port =3D port; + if (user) { + slirp->proxy_user =3D g_strdup(user); + } + if (password) { + slirp->proxy_password =3D g_strdup(password); + } + + return 0; +} + ssize_t slirp_send(struct socket *so, const void *buf, size_t len, int fla= gs) { if (so->s =3D=3D -1 && so->extra) { diff --git a/slirp/slirp.h b/slirp/slirp.h index 3877f66..9db58ed 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]; =20 + /* proxy */ + char *proxy_server; + int proxy_port; + char *proxy_user; + char *proxy_password; + ArpTable arp_table; NdpTable ndp_table; =20 diff --git a/slirp/socket.h b/slirp/socket.h index 8feed2a..232f8e5 100644 --- a/slirp/socket.h +++ b/slirp/socket.h @@ -8,6 +8,8 @@ #ifndef SLIRP_SOCKET_H #define SLIRP_SOCKET_H =20 +#include "socks5.h" + #define SO_EXPIRE 240000 #define SO_EXPIREFAST 10000 =20 @@ -70,6 +72,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; }; =20 =20 diff --git a/slirp/socks5.c b/slirp/socks5.c new file mode 100644 index 0000000..813c3db --- /dev/null +++ b/slirp/socks5.c @@ -0,0 +1,328 @@ +/* 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 "socks5.h" + +#define SOCKS_LEN_MAX 0xff + +#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 + +static int socks5_proxy_connect(int fd, const char *server, int port) +{ + struct sockaddr_in saddr; + struct hostent *he; + + he =3D gethostbyname(server); + if (he =3D=3D NULL) { + errno =3D EINVAL; + return -1; + } + /* TODO: IPv6 */ + saddr.sin_family =3D AF_INET; + saddr.sin_addr =3D *(struct in_addr *)he->h_addr; + saddr.sin_port =3D 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 =3D 0; + + cmd[len++] =3D SOCKS_VERSION_5; + if (user && password) { + cmd[len++] =3D 2; + cmd[len++] =3D SOCKS5_AUTH_METHOD_NONE; + cmd[len++] =3D SOCKS5_AUTH_METHOD_PASSWORD; + } else { + cmd[len++] =3D 1; + cmd[len++] =3D 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, 2, 0) !=3D 2) { + return -1; + } + + if (reply[0] !=3D SOCKS_VERSION_5) { + errno =3D EINVAL; + return -1; + } + + return reply[1]; +} + +static int socks5_send_password(int fd, const char *user, + const char *password) +{ + uint8_t *cmd; + int len =3D 0, ulen, plen; + + if (user =3D=3D NULL || password =3D=3D NULL) { + errno =3D EINVAL; + return -1; + } + + ulen =3D strlen(user); + plen =3D strlen(password); + if (ulen > SOCKS_LEN_MAX || plen > SOCKS_LEN_MAX) { + errno =3D EINVAL; + return -1; + } + + cmd =3D alloca(3 + ulen + plen); + + cmd[len++] =3D SOCKS5_AUTH_PASSWORD_VERSION; + cmd[len++] =3D ulen; + memcpy(cmd + len, user, ulen); + len +=3D ulen; + cmd[len++] =3D 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, 2, 0) !=3D 2) { + return -1; + } + + /* reply[0] is the subnegociation version number: 0x01 + * reply[1] is the status + */ + if (reply[0] !=3D SOCKS5_AUTH_PASSWORD_VERSION || + reply[1] !=3D SOCKS5_AUTH_PASSWORD_SUCCESS) { + errno =3D 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 =3D 0; + + cmd[len++] =3D SOCKS_VERSION_5; + cmd[len++] =3D SOCKS5_CMD_CONNECT; + cmd[len++] =3D 0; /* reserved */ + + switch (addr->ss_family) { + case AF_INET: { + struct sockaddr_in *a =3D (struct sockaddr_in *)addr; + cmd[len++] =3D SOCKS5_ATYPE_IPV4; + memcpy(cmd + len, &a->sin_addr, 4); + len +=3D 4; + memcpy(cmd + len, &a->sin_port, 2); + len +=3D 2; + } + break; + case AF_INET6: { + struct sockaddr_in6 *a =3D (struct sockaddr_in6 *)addr; + cmd[len++] =3D SOCKS5_ATYPE_IPV6; + memcpy(cmd + len, &a->sin6_addr, 16); + len +=3D 16; + memcpy(cmd + len, &a->sin6_port, 2); + len +=3D 2; + } + break; + default: + errno =3D 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, 4, 0) !=3D 4) { + return -1; + } + + if (reply[0] !=3D SOCKS_VERSION_5) { + errno =3D EINVAL; + return -1; + } + + if (reply[1] !=3D SOCKS5_CMD_SUCCESS) { + errno =3D EINVAL; + return -1; + } + + switch (reply[3]) { + case SOCKS5_ATYPE_IPV4: + if (recv(fd, reply + 4, 6, 0) !=3D 6) { + return -1; + } + break; + case SOCKS5_ATYPE_IPV6: + if (recv(fd, reply + 4, 18, 0) !=3D 18) { + return -1; + } + break; + case SOCKS5_ATYPE_FQDN: + if (recv(fd, reply + 4, 1, 0) !=3D 1) { + return -1; + } + if (recv(fd, reply + 5, + reply[4], 0) !=3D reply[4]) { + return -1; + } + break; + default: + errno =3D EINVAL; + return -1; + } + return 0; +} + +int socks5_socket(socks5_state_t *state) +{ + *state =3D 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 !=3D SOCKS5_STATE_CONNECT) { + *state =3D SOCKS5_STATE_NONE; + errno =3D EINVAL; + return -1; + } + + *state =3D 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 =3D socks5_send_negociate(fd, user, password); + if (ret < 0) { + return ret; + } + ret =3D 0; + *state =3D SOCKS5_STATE_NEGOCIATING; + break; + case SOCKS5_STATE_AUTHENTICATE: + ret =3D socks5_send_password(fd, user, password); + if (ret < 0) { + return ret; + } + ret =3D 0; + *state =3D SOCKS5_STATE_AUTHENTICATING; + break; + case SOCKS5_STATE_ESTABLISH: + ret =3D socks5_send_connect(fd, &addr); + if (ret < 0) { + return ret; + } + ret =3D 0; + *state =3D SOCKS5_STATE_ESTABLISHING; + break; + case SOCKS5_STATE_NONE: + ret =3D 1; + break; + default: + ret =3D 0; + break; + } + return ret; +} + +void socks5_recv(int fd, socks5_state_t *state) +{ + int ret; + + switch (*state) { + case SOCKS5_STATE_NEGOCIATING: + switch (socks5_recv_negociate(fd)) { + case SOCKS5_AUTH_METHOD_NONE: /* no authentification */ + *state =3D SOCKS5_STATE_ESTABLISH; + break; + case SOCKS5_AUTH_METHOD_PASSWORD: /* username/password */ + *state =3D SOCKS5_STATE_AUTHENTICATE; + break; + default: + break; + } + break; + case SOCKS5_STATE_AUTHENTICATING: + ret =3D socks5_recv_password(fd); + if (ret >=3D 0) { + *state =3D SOCKS5_STATE_ESTABLISH; + } + break; + case SOCKS5_STATE_ESTABLISHING: + ret =3D socks5_recv_connect(fd); + if (ret >=3D 0) { + *state =3D SOCKS5_STATE_NONE; + } + break; + default: + break; + } +} diff --git a/slirp/socks5.h b/slirp/socks5.h new file mode 100644 index 0000000..24ff1d7 --- /dev/null +++ b/slirp/socks5.h @@ -0,0 +1,24 @@ +#ifndef SOCKS5_H +#define SOCKS5_H + +#include +#include + +typedef enum { + SOCKS5_STATE_NONE =3D 0, + SOCKS5_STATE_CONNECT, + SOCKS5_STATE_NEGOCIATE, + SOCKS5_STATE_NEGOCIATING, + SOCKS5_STATE_AUTHENTICATE, + SOCKS5_STATE_AUTHENTICATING, + SOCKS5_STATE_ESTABLISH, + SOCKS5_STATE_ESTABLISHING, +} 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 @@ =20 #include "qemu/osdep.h" #include "slirp.h" +#include "socks5.h" =20 /* 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=3D0; + Slirp *slirp =3D so->slirp; =20 DEBUG_CALL("tcp_fconnect"); DEBUG_ARG("so =3D %p", so); =20 - ret =3D so->s =3D qemu_socket(af, SOCK_STREAM, 0); + /* pass all non-local traffic through the proxy */ + if (slirp->proxy_server && + !(af =3D=3D AF_INET && + (so->so_faddr.s_addr & slirp->vnetwork_mask.s_addr) =3D=3D + slirp->vnetwork_addr.s_addr) && + !(af =3D=3D AF_INET6 && in6_equal_host(&so->so_faddr6))) { + ret =3D so->s =3D socks5_socket(&so->so_proxy_state); + } else { + ret =3D so->s =3D qemu_socket(af, SOCK_STREAM, 0); + } + if (ret >=3D 0) { int opt, s=3Dso->s; struct sockaddr_storage addr; @@ -413,8 +425,12 @@ int tcp_fconnect(struct socket *so, unsigned short af) sotranslate_out(so, &addr); =20 /* We don't care what port we get */ - ret =3D connect(s, (struct sockaddr *)&addr, sockaddr_size(&addr)); - + if (so->so_proxy_state) { + ret =3D socks5_connect(s, slirp->proxy_server, slirp->proxy_port, + &so->so_proxy_state); + } else { + ret =3D 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; } =20 + /* 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) !=3D + 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) } =20 ip =3D 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 -=3D iphlen; m->m_data +=3D iphlen; uh =3D mtod(m, struct udphdr *); --=20 2.9.3