From nobody Thu May 2 16:27:22 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 1494358408041304.09871466193135; Tue, 9 May 2017 12:33:28 -0700 (PDT) Received: from localhost ([::1]:39043 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d8Asw-0005vh-N4 for importer@patchew.org; Tue, 09 May 2017 15:33:26 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:53452) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1d8Ar7-0004R5-QY for qemu-devel@nongnu.org; Tue, 09 May 2017 15:31:36 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1d8Ar2-0000ap-Vr for qemu-devel@nongnu.org; Tue, 09 May 2017 15:31:33 -0400 Received: from mout.kundenserver.de ([217.72.192.73]:63007) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1d8Ar2-0000Zf-GQ for qemu-devel@nongnu.org; Tue, 09 May 2017 15:31:28 -0400 Received: from localhost.localdomain ([78.238.229.36]) by mrelayeu.kundenserver.de (mreue102 [212.227.15.183]) with ESMTPSA (Nemesis) id 0MCqZf-1dGjZe0chX-009eQf; Tue, 09 May 2017 21:31:17 +0200 From: Laurent Vivier To: Samuel Thibault Date: Tue, 9 May 2017 21:31:12 +0200 Message-Id: <20170509193112.16613-2-laurent@vivier.eu> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170509193112.16613-1-laurent@vivier.eu> References: <20170509193112.16613-1-laurent@vivier.eu> X-Provags-ID: V03:K0:DBpdp10fdV3pK+itj80+cFOsZE7IhB8xIWQNq5nco6rV6lyqFM2 oY1JLV4tyKTmPM23k93+toKVZdS4J3X3jrw1ypChaiC/aGEJn/TB7BnxxyoQIhOkem/AIyF G0hmC73YeD8QKUNj3bMAguo1J1c9CDAe/HGyASTIwhqQ2CXCBXDpPfsnHUM7ZxWr1NM6C17 ZasZK5GNqcrAFyG4EF+RQ== X-UI-Out-Filterresults: notjunk:1;V01:K0:XDQ9NdkufkM=:gHJC0Yp8MFhMWpMSr+9eYI EWssbnI4CYXXTyD1i/rYLlmQZHuL1426WVYyF1/W8Ff2Q+3x+Sxd7wLh0sP6q12JtIkcAHXxx hVaZBLcPBVf71kt4ArmL7hfAjwvDQjbqV1yO9XECHIuefnMSFaf0CAdrLwDEEHvWL6lfV5NQT ozdPTSvVLKWKYEY5J++T6kM5GouRtPo5ndbiNh/9+6QgU/WgV1jjj++WLR3ApYwzmxQuQhyVh KBTQVbAi2J+RO3kVW1atQ8DO/oOCHYz1Jp2YB8KLeceJ3PSgvrGXx7N0Q/GJh90SOThNckjdW V1+n4RjIzTZrFEwzeI3YYpIZAb5WMWYxCD+03za2A4n1/d7E0cGfXUMmeOiIlI97r27D4F/HN Z7lpQ8BcEkJXeYTQKo6NemoOPdG/IkmOpNeqKIyVjAVUcKUEPTaOsJ88VtNiU6OZcOMuNRIJs e4IfHNYiJV/EKpN4G0kPUR8C76qCgvccApiQaxKemgTu9RdUdpB8BSljoySftz90r1bHIgXWC VIsiPiYcB6ssr0YJbBazywneF887J+rVppqgfZoQJnnu8VR8tjN5rcogP8Ge2fDLmjICwLLN+ U5e41vT5BoVc5Ab9vBlEWEHLLQbePwTIoPDGEgWJdxe9lGOwXsQFS50Iksj4YZqkolJDcCGXL V9qSoJ7dozVK8t9H0gvBHYow4kR9/skggtHSzr378Q5LOurjw7gmO7Vi2gG8DNRKMa0c= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 217.72.192.73 Subject: [Qemu-devel] [PATCH v5 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: qemu-devel@nongnu.org, Jason Wang , Laurent Vivier , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= 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 | 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" =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; @@ -882,7 +916,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 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'] } } =20 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=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 @@ -1920,6 +1921,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 28049b0..3cf6c8d 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 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 =3D=3D slirp->vhost_addr.s_addr || ip->ip_dst.s_addr =3D=3D 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= ); =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 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" =20 #ifndef _WIN32 #include @@ -442,6 +443,10 @@ void slirp_pollfds_fill(GArray *pollfds, uint32_t *tim= eout) .fd =3D so->s, .events =3D G_IO_OUT | G_IO_ERR, }; + if (so->so_proxy_state && + so->so_proxy_state !=3D SOCKS5_STATE_ERROR) { + pfd.events |=3D G_IO_IN; + } so->pollfds_idx =3D pollfds->len; g_array_append_val(pollfds, pfd); continue; @@ -617,6 +622,11 @@ 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_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_e= rror) /* * Check for non-blocking, still-connecting sockets */ + if (so->so_state & SS_ISFCONNECTING) { + if (so->so_proxy_state) { + ret =3D socks5_send(so->s, slirp->proxy_user, + slirp->proxy_password, so->f= host.ss, + &so->so_proxy_state); + if (ret < 0) { + goto write_error; + } + continue; + } /* Connected */ so->so_state &=3D ~SS_ISFCONNECTING; =20 ret =3D send(so->s, (const void *) &ret, 0, 0); if (ret < 0) { +write_error: /* XXXXX Must fix, zero bytes is a NOP */ if (errno =3D=3D EAGAIN || errno =3D=3D EWOULD= BLOCK || errno =3D=3D EINPROGRESS || errno =3D=3D E= NOTCONN) { @@ -1073,6 +1094,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_ERROR) { + 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 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]; =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 2f224bc..6d9976b 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 @@ -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; }; =20 =20 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 + * + * 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 =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, SOCKS5_NEGOCIATE_HDR_LEN, 0) !=3D + SOCKS5_NEGOCIATE_HDR_LEN) { + return -1; + } + + if (reply[0] !=3D SOCKS_VERSION_5) { + errno =3D 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 =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, SOCKS5_PASSWD_HDR_LEN, 0) !=3D SOCKS5_PASSWD_HDR_L= EN) { + 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, sizeof(struct in_addr)); + len +=3D sizeof(struct in_addr); + memcpy(cmd + len, &a->sin_port, sizeof(in_port_t)); + len +=3D sizeof(in_port_t); + } + 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, sizeof(struct in6_addr)); + len +=3D sizeof(struct in6_addr); + memcpy(cmd + len, &a->sin6_port, sizeof(in_port_t)); + len +=3D sizeof(in_port_t); + } + 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, SOCKS5_CONNECT_HDR_LEN, 0) !=3D SOCKS5_CONNECT_HDR= _LEN) { + return -1; + } + + if (reply[0] !=3D SOCKS_VERSION_5) { + qemu_log_mask(LOG_GUEST_ERROR, "Invalid SOCKS version: %d\n", repl= y[0]); + errno =3D EINVAL; + return -1; + } + + if (reply[1] !=3D SOCKS5_CMD_SUCCESS) { + qemu_log_mask(LOG_GUEST_ERROR, "SOCKS5 connection error: %d\n", + reply[1]); + errno =3D EINVAL; + return -1; + } + + switch (reply[3]) { + case SOCKS5_ATYPE_IPV4: + if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN, + SOCKS5_ATYPE_IPV4_LEN, 0) !=3D SOCKS5_ATYPE_IPV4_LEN) { + return -1; + } + break; + case SOCKS5_ATYPE_IPV6: + if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN, + SOCKS5_ATYPE_IPV6_LEN, 0) !=3D SOCKS5_ATYPE_IPV6_LEN) { + return -1; + } + break; + case SOCKS5_ATYPE_FQDN: + if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN, 1, 0) !=3D 1) { + return -1; + } + if (recv(fd, reply + SOCKS5_CONNECT_HDR_LEN + 1, + reply[SOCKS5_CONNECT_HDR_LEN], 0) !=3D + reply[SOCKS5_CONNECT_HDR_LEN]) { + return -1; + } + qemu_log_mask(LOG_GUEST_ERROR, "Unsupported SOCKS5 ATYPE: FDDN\n"); + 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; + } + *state =3D SOCKS5_STATE_NEGOCIATING; + break; + case SOCKS5_STATE_AUTHENTICATE: + ret =3D socks5_send_password(fd, user, password); + if (ret < 0) { + return ret; + } + *state =3D SOCKS5_STATE_AUTHENTICATING; + break; + case SOCKS5_STATE_ESTABLISH: + ret =3D socks5_send_connect(fd, &addr); + if (ret < 0) { + return ret; + } + *state =3D 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 =3D SOCKS5_STATE_ERROR; + errno =3D EINVAL; + return -1; + } + return 0; +} + +void socks5_recv(int fd, socks5_state_t *state) +{ + int ret; + + switch (*state) { + case SOCKS5_STATE_NEGOCIATING: + ret =3D socks5_recv_negociate(fd); + if (ret < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "SOCKS5 AUTH method error: %d\n", errno); + *state =3D SOCKS5_STATE_ERROR; + return; + } + switch (ret) { + 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: + qemu_log_mask(LOG_GUEST_ERROR, + "SOCKS5 unsupported AUTH method: %d\n", ret); + *state =3D SOCKS5_STATE_ERROR; + break; + } + break; + case SOCKS5_STATE_AUTHENTICATING: + ret =3D socks5_recv_password(fd); + if (ret < 0) { + *state =3D SOCKS5_STATE_ERROR; + return; + } + *state =3D SOCKS5_STATE_ESTABLISH; + break; + case SOCKS5_STATE_ESTABLISHING: + ret =3D socks5_recv_connect(fd); + if (ret < 0) { + *state =3D SOCKS5_STATE_ERROR; + return; + } + *state =3D 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 =3D 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 + * + * 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 =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_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 @@ =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