From nobody Sun May 5 00:00:51 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 1490639018943495.80060945911055; Mon, 27 Mar 2017 11:23:38 -0700 (PDT) Received: from localhost ([::1]:48435 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1csZIn-0000jJ-M2 for importer@patchew.org; Mon, 27 Mar 2017 14:23:37 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38018) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1csZH8-0007vI-Rr for qemu-devel@nongnu.org; Mon, 27 Mar 2017 14:21:57 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1csZH4-00030H-RR for qemu-devel@nongnu.org; Mon, 27 Mar 2017 14:21:54 -0400 Received: from mout.kundenserver.de ([212.227.126.135]:51340) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1csZH4-0002zw-8q for qemu-devel@nongnu.org; Mon, 27 Mar 2017 14:21:50 -0400 Received: from localhost.localdomain ([78.238.229.36]) by mrelayeu.kundenserver.de (mreue002 [212.227.15.167]) with ESMTPSA (Nemesis) id 0LhoMU-1cOTdW3e0g-00nC28; Mon, 27 Mar 2017 20:21:41 +0200 From: Laurent Vivier To: Samuel Thibault Date: Mon, 27 Mar 2017 20:21:37 +0200 Message-Id: <20170327182137.7006-2-laurent@vivier.eu> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170327182137.7006-1-laurent@vivier.eu> References: <20170327182137.7006-1-laurent@vivier.eu> X-Provags-ID: V03:K0:59SbLQ1xkmGBH/Qzf4VlraxZg2bTmEZtGxXKGkkxBDt1y2x/6Up 9eU+cTin3Z5V1aoxMlJWfgiSpUBLAtZdm30oLgb7oqThIFXm/ihzAug7sG/Ghhn+CqOUBye nq7MW9e2C88Yl6N3QM2r07nD5Zz9rlR554cCPmKGvha5Tz1wRVRBUd/AYXLndXFjJiLhOqz 0VJ7PnBO/vkMMlIxy4Ylg== X-UI-Out-Filterresults: notjunk:1;V01:K0:XS5Adt91GFo=:D7yVs1EP1RluzBCLq+MjO2 C3B7UdrWBGsG35aSHu/frubSOnZ/2qJWJe3mJ0k2XJ0bxVIYKsrV75euLywXrRGPy6Q/wXSbD XEajxyaMG1vAxiYjBG9qODH3rzJD/wokF51uFJulRisZb+GYufmYuuumIOvfGFnn9lWzqdxJw MT7bwe8lUfimMQRkrt7M/5jGgtZ8FBq4S25bTHMjz5OV0zLZqTn0g9FJHQoEJ+mHkT8A9YbAA 8Br2orCCmXZtBIm1mmm44D8pDgemxcCybQC3rES/n/S6Tlx+zomH00LuAzamz3bZK4I3I89Us 7o6jMEliQKQPGw1gVZQmr8WtcwUJNXK53V/K4jzf8veliSGmZmODK0nzOp/87NIFeL+zjQ+yZ /ASlrjdB3WKHNFJYRenTBbDHA87xxlIjOIeJGvkCh6xDElxqF1dfDD7gZv8U2LLWFyvC549xT wPIKnjWSmldHpwyBp4al62HeNjkb/d8Unw0KhJvc/NrIfSbG+cAyLLhevUfgqaO+NJ5pQYByY IYxAVtBls+PqYWw4ac4xx0p08feMpXdVIMclNruXUm301CVyIbB9SWKwUn2LqdzLFSsmXhYM+ UDADxdAmRcMkyx5fOBFKloNlmrNTVCCEUZvv2vAyFyI1NYOaWwiVs8s7OpauouoN6JUHgJYRj XlA6fLYYBI3jcQ5fKqc59eEeC0mk+z4mkyOcClfhgVST2y1q3at/fjN+9evkk8K5WA48= X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 212.227.126.135 Subject: [Qemu-devel] [PATCH 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 to use 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 | 30 +++++- qapi-schema.json | 3 + qemu-options.hx | 11 ++ slirp/Makefile.objs | 2 +- slirp/ip_icmp.c | 2 +- slirp/libslirp.h | 3 + slirp/slirp.c | 66 +++++++++++- slirp/slirp.h | 6 ++ slirp/socket.h | 4 + slirp/socks5.c | 283 ++++++++++++++++++++++++++++++++++++++++++++++++= ++++ slirp/socks5.h | 85 ++++++++++++++++ slirp/tcp_subr.c | 21 +++- slirp/udp.c | 9 ++ slirp/udp6.c | 2 +- 14 files changed, 515 insertions(+), 12 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..7299b54 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -139,6 +139,26 @@ 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_p= asswd) +{ + InetSocketAddress *addr; + int ret; + + if (proxy_server =3D=3D NULL) { + return 0; + } + + addr =3D inet_parse(proxy_server, &error_fatal); + + ret =3D slirp_add_proxy(s->slirp, addr->host, atoi(addr->port), + proxy_user, proxy_passwd); + + qapi_free_InetSocketAddress(addr); + + return ret; +} + static NetClientInfo net_slirp_info =3D { .type =3D NET_CLIENT_DRIVER_USER, .size =3D sizeof(SlirpState), @@ -155,7 +175,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_passwd) { /* default settings according to historic slirp */ struct in_addr net =3D { .s_addr =3D htonl(0x0a000200) }; /* 10.0.2.0= */ @@ -361,6 +382,10 @@ static int net_slirp_init(NetClientState *peer, const = char *model, } #endif =20 + if (net_slirp_add_proxy(s, proxy_server, proxy_user, proxy_passwd) < 0= ) { + goto error; + } + s->exit_notifier.notify =3D slirp_smb_exit; qemu_add_exit_notifier(&s->exit_notifier); return 0; @@ -878,7 +903,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_passwd); =20 while (slirp_configs) { config =3D slirp_configs; diff --git a/qapi-schema.json b/qapi-schema.json index 68a4327..62eb23b 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3680,6 +3680,9 @@ '*ipv6-dns': 'str', '*smb': 'str', '*smbserver': 'str', + '*proxy-server': 'str', + '*proxy-user': 'str', + '*proxy-passwd': 'str', '*hostfwd': ['String'], '*guestfwd': ['String'] } } =20 diff --git a/qemu-options.hx b/qemu-options.hx index 99af8ed..472ab6a 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-passwd= =3Dpasswd]]\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-= passwd=3D@var{passwd}]] +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-passwd. + +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..d16f2b0 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 *passwd); + /* 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 60539de..0385e2f 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_passwd, so->fhost.s= s, + &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,48 @@ 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 *passwd) +{ + int fd; + socks5_state_t state; + struct sockaddr_storage addr; + + /* check the connection */ + + 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, passwd, 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 (passwd) { + slirp->proxy_passwd =3D g_strdup(passwd); + } + + 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..9478a83 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_passwd; + 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..9f2d2f1 --- /dev/null +++ b/slirp/socks5.c @@ -0,0 +1,283 @@ +/* some parts from nmap/ncat GPLv2 */ + +#include "qemu/osdep.h" +#include "qemu/sockets.h" + +#include "socks5.h" + +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; + } + 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 *pas= swd) +{ + struct socks5_connect socks5msg; + int len; + + memset(&socks5msg, 0, sizeof(socks5msg)); + socks5msg.ver =3D SOCKS5_VERSION; + socks5msg.nmethods =3D 1; + socks5msg.methods[0] =3D SOCKS5_AUTH_NONE; + len =3D 3; + + if (user && passwd) { + socks5msg.nmethods++; + socks5msg.methods[1] =3D SOCKS5_AUTH_USERPASS; + len++; + } + + return send(fd, (char *)&socks5msg, len, 0); +} + +static int socks5_recv_negociate(int fd) +{ + char socksbuf[2]; + + /* socksbuf[0] is the protocol version number: 0x05 + * socksbuf[1] is the selected authentification protocol + */ + + if (recv(fd, socksbuf, 2, 0) !=3D 2) { + return -1; + } + + if (socksbuf[0] !=3D SOCKS5_VERSION) { + errno =3D EINVAL; + return -1; + } + + return socksbuf[1]; +} + +static int socks5_send_authenticate(int fd, const char *user, + const char *passwd) +{ + struct socks5_auth socks5auth; + int len; + + if (user =3D=3D NULL || passwd =3D=3D NULL) { + errno =3D EINVAL; + return -1; + } + + if (strlen(user) + strlen(passwd) > SOCKS_BUFF_SIZE - 2) { + errno =3D EINVAL; + return -1; + } + + socks5auth.ver =3D 1; + socks5auth.data[0] =3D strlen(user); + memcpy(socks5auth.data + 1, user, strlen(user)); + len =3D 2 + strlen(user); + + socks5auth.data[len - 1] =3D strlen(passwd); + memcpy(socks5auth.data + len, passwd, strlen(passwd)); + len +=3D 1 + strlen(passwd); + + return send(fd, (char *)&socks5auth, len, 0); +} + +static int socks5_recv_authenticate(int fd) +{ + char socksbuf[2]; + if (recv(fd, socksbuf, 2, 0) !=3D 2) { + return -1; + } + if (socksbuf[0] !=3D 1 || socksbuf[1] !=3D 0) { + errno =3D EINVAL; + return -1; + } + return 0; +} + +static int socks5_send_connect(int fd, struct sockaddr_storage *addr) +{ + struct socks5_request socks5msg; + int len; + + memset(&socks5msg, 0, sizeof(socks5msg)); + + socks5msg.ver =3D SOCKS5_VERSION; + socks5msg.cmd =3D SOCKS_CONNECT; + socks5msg.rsv =3D 0; + + switch (addr->ss_family) { + case AF_INET: { + struct sockaddr_in *a =3D (struct sockaddr_in *)addr; + + socks5msg.atyp =3D SOCKS5_ATYP_IPv4; + memcpy(socks5msg.dst, &a->sin_addr, sizeof(a->sin_addr)); + len =3D sizeof(a->sin_addr); + memcpy(socks5msg.dst + len, &a->sin_port, sizeof(a->sin_port)); + len +=3D sizeof(a->sin_port); + } + break; + case AF_INET6: { + struct sockaddr_in6 *a =3D (struct sockaddr_in6 *)addr; + + socks5msg.atyp =3D SOCKS5_ATYP_IPv6; + memcpy(socks5msg.dst, &a->sin6_addr, sizeof(a->sin6_addr)); + len =3D sizeof(a->sin6_addr); + memcpy(socks5msg.dst + len, &a->sin6_port, sizeof(a->sin6_port= )); + len +=3D sizeof(a->sin6_port); + } + break; + default: + errno =3D EINVAL; + return -1; + } + len +=3D 4; + + return send(fd, (char *)&socks5msg, len, 0); +} + +static int socks5_recv_connect(int fd) +{ + struct socks5_request socks5msg; + + if (recv(fd, &socks5msg, 4, 0) !=3D 4) { + return -1; + } + + if (socks5msg.ver !=3D SOCKS5_VERSION) { + errno =3D EINVAL; + return -1; + } + + if (socks5msg.cmd !=3D 0x00) { + errno =3D EINVAL; + return -1; + } + + switch (socks5msg.atyp) { + case SOCKS5_ATYP_IPv4: + if (recv(fd, socks5msg.dst, 6, 0) !=3D 6) { + return -1; + } + break; + case SOCKS5_ATYP_IPv6: + if (recv(fd, socks5msg.dst, 18, 0) !=3D 18) { + return -1; + } + break; + case SOCKS5_ATYP_NAME: + if (recv(fd, socks5msg.dst, 1, 0) !=3D 1) { + return -1; + } + if (recv(fd, socks5msg.dst + 1, + socks5msg.dst[0], 0) !=3D socks5msg.dst[0]) { + 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 *passwd, + struct sockaddr_storage addr, socks5_state_t *state) +{ + int ret; + + switch (*state) { + case SOCKS5_STATE_NEGOCIATE: + ret =3D socks5_send_negociate(fd, user, passwd); + if (ret < 0) { + return ret; + } + ret =3D 0; + *state =3D SOCKS5_STATE_NEGOCIATING; + break; + case SOCKS5_STATE_AUTHENTICATE: + ret =3D socks5_send_authenticate(fd, user, passwd); + 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_NONE: /* no authentification */ + *state =3D SOCKS5_STATE_ESTABLISH; + break; + case SOCKS5_AUTH_USERPASS: /* username/password */ + *state =3D SOCKS5_STATE_AUTHENTICATE; + break; + default: + break; + } + break; + case SOCKS5_STATE_AUTHENTICATING: + ret =3D socks5_recv_authenticate(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..895cc88 --- /dev/null +++ b/slirp/socks5.h @@ -0,0 +1,85 @@ +#ifndef SOCKS5_H +#define SOCKS5_H + +#include +#include + +/* some parts from nmap/ncat GPLv2 */ + +#define SOCKS_BUFF_SIZE 512 + +struct socks4_data { + char version; + char type; + unsigned short port; + uint32_t address; + char data[SOCKS_BUFF_SIZE]; /* to hold FQDN and username */ +} __attribute__((packed)); + +struct socks5_connect { + char ver; + char nmethods; + char methods[3]; +} __attribute__((packed)); + +struct socks5_auth { + char ver; /* must be always 1 */ + char data[SOCKS_BUFF_SIZE]; +} __attribute__((packed)); + +struct socks5_request { + char ver; + char cmd; + char rsv; + char atyp; + char dst[SOCKS_BUFF_SIZE]; /* addr/name and port info */ +} __attribute__((packed)); + +/* defines */ + +/* Default port for SOCKS5 */ +#define DEFAULT_SOCKS5_PORT 1080 + +/* SOCKS4 protocol responses */ +#define SOCKS4_VERSION 4 +#define SOCKS_CONNECT 1 +#define SOCKS_BIND 2 +#define SOCKS4_CONN_ACC 90 +#define SOCKS4_CONN_REF 91 +#define SOCKS4_CONN_IDENT 92 +#define SOCKS4_CONN_IDENTDIFF 93 + +/* SOCKS5 protocol */ +#define SOCKS5_VERSION 5 +#define SOCKS5_AUTH_NONE 0 +#define SOCKS5_AUTH_GSSAPI 1 +#define SOCKS5_AUTH_USERPASS 2 +#define SOCKS5_AUTH_FAILED 255 +#define SOCKS5_ATYP_IPv4 1 +#define SOCKS5_ATYP_NAME 3 +#define SOCKS5_ATYP_IPv6 4 + + +/* Length of IPv6 address */ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +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 *passwd, + 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..5e394fd 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,21 @@ 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); + /* local traffic doesn't go to 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)) { + 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 +424,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..995181d 100644 --- a/slirp/udp6.c +++ b/slirp/udp6.c @@ -22,7 +22,7 @@ void udp6_input(struct mbuf *m) DEBUG_CALL("udp6_input"); DEBUG_ARG("m =3D %lx", (long)m); =20 - if (slirp->restricted) { + if (slirp->restricted || slirp->proxy_server) { goto bad; } =20 --=20 2.9.3