proxy.c revision daef40d247c31e2073c0ed0ec55fac77a4b89ac3
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync/* -*- indent-tabs-mode: nil; -*- */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#define LOG_GROUP LOG_GROUP_NAT_SERVICE
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "winutils.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "proxy.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "proxy_pollmgr.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "portfwd.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "lwip/opt.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "lwip/sys.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include "lwip/tcpip.h"
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#ifndef RT_OS_WINDOWS
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <sys/poll.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <sys/socket.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <netinet/in.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <arpa/inet.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <fcntl.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <stdio.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <iprt/string.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <unistd.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#include <err.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#else
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync# include <iprt/string.h>
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#endif
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#if defined(SOCK_NONBLOCK) && defined(RT_OS_NETBSD) /* XXX: PR kern/47569 */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync# undef SOCK_NONBLOCK
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#endif
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#ifndef __arraycount
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync# define __arraycount(a) (sizeof(a)/sizeof(a[0]))
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#endif
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncstatic SOCKET proxy_create_socket(int, int);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncvolatile struct proxy_options *g_proxy_options;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncstatic sys_thread_t pollmgr_tid;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync/* XXX: for mapping loopbacks to addresses in our network (ip4) */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncstruct netif *g_proxy_netif;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync/*
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * Called on the lwip thread (aka tcpip thread) from tcpip_init() via
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * its "tcpip_init_done" callback. Raw API is ok to use here
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * (e.g. rtadvd), but netconn API is not.
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncvoid
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncproxy_init(struct netif *proxy_netif, struct proxy_options *opts)
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync{
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync int status;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync LWIP_ASSERT1(opts != NULL);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync LWIP_UNUSED_ARG(proxy_netif);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync g_proxy_options = opts;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync g_proxy_netif = proxy_netif;
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#if 1
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync proxy_rtadvd_start(proxy_netif);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#endif
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync /*
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * XXX: We use stateless DHCPv6 only to report IPv6 address(es) of
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * nameserver(s). Since we don't yet support IPv6 addresses in
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * HostDnsService, there's no point in running DHCPv6.
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#if 0
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync dhcp6ds_init(proxy_netif);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync#endif
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync if (opts->tftp_root != NULL) {
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync tftpd_init(proxy_netif, opts->tftp_root);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync }
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync status = pollmgr_init();
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync if (status < 0) {
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync errx(EXIT_FAILURE, "failed to initialize poll manager");
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync /* NOTREACHED */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync }
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pxtcp_init();
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pxudp_init();
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync portfwd_init();
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pxdns_init(proxy_netif);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pxping_init(proxy_netif, opts->icmpsock4, opts->icmpsock6);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pollmgr_tid = sys_thread_new("pollmgr_thread",
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync pollmgr_thread, NULL,
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync DEFAULT_THREAD_STACKSIZE,
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync DEFAULT_THREAD_PRIO);
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync if (!pollmgr_tid) {
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync errx(EXIT_FAILURE, "failed to create poll manager thread");
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync /* NOTREACHED */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync }
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync}
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync/**
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * Send static callback message from poll manager thread to lwip
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * thread, scheduling a function call in lwip thread context.
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync *
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * XXX: Existing lwip api only provides non-blocking version for this.
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * It may fail when lwip thread is not running (mbox invalid) or if
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync * post failed (mbox full). How to handle these?
deb4998ba50060c48cce222fd18a8eed053918d7vboxsync */
deb4998ba50060c48cce222fd18a8eed053918d7vboxsyncvoid
proxy_lwip_post(struct tcpip_msg *msg)
{
struct tcpip_callback_msg *m;
err_t error;
LWIP_ASSERT1(msg != NULL);
/*
* lwip plays games with fake incomplete struct tag to enforce API
*/
m = (struct tcpip_callback_msg *)msg;
error = tcpip_callbackmsg(m);
if (error == ERR_VAL) {
/* XXX: lwip thread is not running (mbox invalid) */
LWIP_ASSERT1(error != ERR_VAL);
}
LWIP_ASSERT1(error == ERR_OK);
}
/**
* Create a non-blocking socket. Disable SIGPIPE for TCP sockets if
* possible. On Linux it's not possible and should be disabled for
* each send(2) individually.
*/
static SOCKET
proxy_create_socket(int sdom, int stype)
{
SOCKET s;
int stype_and_flags;
int status;
LWIP_UNUSED_ARG(status); /* depends on ifdefs */
stype_and_flags = stype;
#if defined(SOCK_NONBLOCK)
stype_and_flags |= SOCK_NONBLOCK;
#endif
/*
* Disable SIGPIPE on disconnected socket. It might be easier to
* forgo it and just use MSG_NOSIGNAL on each send*(2), since we
* have to do it for Linux anyway, but Darwin does NOT have that
* flag (but has SO_NOSIGPIPE socket option).
*/
#if !defined(SOCK_NOSIGPIPE) && !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
#if 0 /* XXX: Solaris has neither, the program should ignore SIGPIPE globally */
#error Need a way to disable SIGPIPE on connection oriented sockets!
#endif
#endif
#if defined(SOCK_NOSIGPIPE)
if (stype == SOCK_STREAM) {
stype_and_flags |= SOCK_NOSIGPIPE;
}
#endif
s = socket(sdom, stype_and_flags, 0);
if (s == INVALID_SOCKET) {
perror("socket");
return INVALID_SOCKET;
}
#if !defined(SOCK_NONBLOCK) && !defined(RT_OS_WINDOWS)
{
int sflags;
sflags = fcntl(s, F_GETFL, 0);
if (sflags < 0) {
perror("F_GETFL");
closesocket(s);
return INVALID_SOCKET;
}
status = fcntl(s, F_SETFL, sflags | O_NONBLOCK);
if (status < 0) {
perror("O_NONBLOCK");
closesocket(s);
return INVALID_SOCKET;
}
}
#endif
#if !defined(SOCK_NOSIGPIPE) && defined(SO_NOSIGPIPE)
if (stype == SOCK_STREAM) {
int on = 1;
const socklen_t onlen = sizeof(on);
status = setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &on, onlen);
if (status < 0) {
perror("SO_NOSIGPIPE");
closesocket(s);
return INVALID_SOCKET;
}
}
#endif
#if defined(RT_OS_WINDOWS)
{
u_long mode = 0;
status = ioctlsocket(s, FIONBIO, &mode);
if (status == SOCKET_ERROR) {
warn("ioctl error: %d\n", WSAGetLastError());
return INVALID_SOCKET;
}
}
#endif
return s;
}
/**
* Create a socket for outbound connection to dst_addr:dst_port.
*
* The socket is non-blocking and TCP sockets has SIGPIPE disabled if
* possible. On Linux it's not possible and should be disabled for
* each send(2) individually.
*/
SOCKET
proxy_connected_socket(int sdom, int stype,
ipX_addr_t *dst_addr, u16_t dst_port)
{
struct sockaddr_in6 dst_sin6;
struct sockaddr_in dst_sin;
struct sockaddr *pdst_sa;
socklen_t dst_sa_len;
void *pdst_addr;
const struct sockaddr *psrc_sa;
socklen_t src_sa_len;
int status;
SOCKET s;
LWIP_ASSERT1(sdom == PF_INET || sdom == PF_INET6);
LWIP_ASSERT1(stype == SOCK_STREAM || stype == SOCK_DGRAM);
DPRINTF(("---> %s ", stype == SOCK_STREAM ? "TCP" : "UDP"));
if (sdom == PF_INET6) {
pdst_sa = (struct sockaddr *)&dst_sin6;
pdst_addr = (void *)&dst_sin6.sin6_addr;
memset(&dst_sin6, 0, sizeof(dst_sin6));
#if HAVE_SA_LEN
dst_sin6.sin6_len =
#endif
dst_sa_len = sizeof(dst_sin6);
dst_sin6.sin6_family = AF_INET6;
memcpy(&dst_sin6.sin6_addr, &dst_addr->ip6, sizeof(ip6_addr_t));
dst_sin6.sin6_port = htons(dst_port);
DPRINTF(("[%RTnaipv6]:%d ", &dst_sin6.sin6_addr, dst_port));
}
else { /* sdom = PF_INET */
pdst_sa = (struct sockaddr *)&dst_sin;
pdst_addr = (void *)&dst_sin.sin_addr;
memset(&dst_sin, 0, sizeof(dst_sin));
#if HAVE_SA_LEN
dst_sin.sin_len =
#endif
dst_sa_len = sizeof(dst_sin);
dst_sin.sin_family = AF_INET;
dst_sin.sin_addr.s_addr = dst_addr->ip4.addr; /* byte-order? */
dst_sin.sin_port = htons(dst_port);
DPRINTF(("%RTnaipv4:%d ", dst_sin.sin_addr.s_addr, dst_port));
}
s = proxy_create_socket(sdom, stype);
if (s == INVALID_SOCKET) {
return INVALID_SOCKET;
}
DPRINTF(("socket %d\n", s));
/* TODO: needs locking if dynamic modifyvm is allowed */
if (sdom == PF_INET6) {
psrc_sa = (const struct sockaddr *)g_proxy_options->src6;
src_sa_len = sizeof(struct sockaddr_in6);
}
else {
psrc_sa = (const struct sockaddr *)g_proxy_options->src4;
src_sa_len = sizeof(struct sockaddr_in);
}
if (psrc_sa != NULL) {
status = bind(s, psrc_sa, src_sa_len);
if (status == SOCKET_ERROR) {
DPRINTF(("socket %d: bind: %s\n", s, strerror(errno)));
closesocket(s);
return INVALID_SOCKET;
}
}
status = connect(s, pdst_sa, dst_sa_len);
if (status == SOCKET_ERROR && errno != EINPROGRESS) {
DPRINTF(("socket %d: connect: %s\n", s, strerror(errno)));
closesocket(s);
return INVALID_SOCKET;
}
return s;
}
/**
* Create a socket for inbound (port-forwarded) connections to
* src_addr (port is part of sockaddr, so not a separate argument).
*
* The socket is non-blocking and TCP sockets has SIGPIPE disabled if
* possible. On Linux it's not possible and should be disabled for
* each send(2) individually.
*
* TODO?: Support v6-mapped v4 so that user can specify she wants
* "udp" and get both versions?
*/
SOCKET
proxy_bound_socket(int sdom, int stype, struct sockaddr *src_addr)
{
SOCKET s;
int on;
const socklen_t onlen = sizeof(on);
int status;
s = proxy_create_socket(sdom, stype);
if (s == INVALID_SOCKET) {
return INVALID_SOCKET;
}
DPRINTF(("socket %d\n", s));
on = 1;
status = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, onlen);
if (status < 0) { /* not good, but not fatal */
warn("SO_REUSEADDR");
}
status = bind(s, src_addr,
sdom == PF_INET ?
sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6));
if (status < 0) {
perror("bind");
closesocket(s);
return INVALID_SOCKET;
}
if (stype == SOCK_STREAM) {
status = listen(s, 5);
if (status < 0) {
perror("listen");
closesocket(s);
return INVALID_SOCKET;
}
}
return s;
}
void
proxy_reset_socket(SOCKET s)
{
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 0;
/* On Windows we can run into issue here, perhaps SO_LINGER isn't enough, and
* we should use WSA{Send,Recv}Disconnect instead.
*
* Links for the reference:
* http://msdn.microsoft.com/en-us/library/windows/desktop/ms738547%28v=vs.85%29.aspx
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4468997
*/
setsockopt(s, SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof(linger));
closesocket(s);
}
int
proxy_sendto(SOCKET sock, struct pbuf *p, void *name, size_t namelen)
{
struct pbuf *q;
size_t i, clen;
#ifndef RT_OS_WINDOWS
struct msghdr mh;
ssize_t nsent;
#else
DWORD nsent;
int rc;
#endif
IOVEC fixiov[8]; /* fixed size (typical case) */
const size_t fixiovsize = sizeof(fixiov)/sizeof(fixiov[0]);
IOVEC *dyniov; /* dynamically sized */
IOVEC *iov;
int error = 0;
/*
* Static iov[] is usually enough since UDP protocols use small
* datagrams to avoid fragmentation, but be prepared.
*/
clen = pbuf_clen(p);
if (clen > fixiovsize) {
/*
* XXX: TODO: check that clen is shorter than IOV_MAX
*/
dyniov = (IOVEC *)malloc(clen * sizeof(*dyniov));
if (dyniov == NULL) {
error = -errno;
goto out;
}
iov = dyniov;
}
else {
dyniov = NULL;
iov = fixiov;
}
for (q = p, i = 0; i < clen; q = q->next, ++i) {
LWIP_ASSERT1(q != NULL);
IOVEC_SET_BASE(iov[i], q->payload);
IOVEC_SET_LEN(iov[i], q->len);
}
#ifndef RT_OS_WINDOWS
memset(&mh, 0, sizeof(mh));
mh.msg_name = name;
mh.msg_namelen = namelen;
mh.msg_iov = iov;
mh.msg_iovlen = clen;
nsent = sendmsg(sock, &mh, 0);
if (nsent < 0) {
error = -errno;
DPRINTF(("%s: fd %d: sendmsg errno %d\n",
__func__, sock, errno));
}
#else
rc = WSASendTo(sock, iov, (DWORD)clen, &nsent, 0,
name, (int)namelen, NULL, NULL);
if (rc == SOCKET_ERROR) {
DPRINTF(("%s: fd %d: sendmsg errno %d\n",
__func__, sock, WSAGetLastError()));
error = -WSAGetLastError();
}
#endif
out:
if (dyniov != NULL) {
free(dyniov);
}
return error;
}
static const char *lwiperr[] = {
"ERR_OK",
"ERR_MEM",
"ERR_BUF",
"ERR_TIMEOUT",
"ERR_RTE",
"ERR_INPROGRESS",
"ERR_VAL",
"ERR_WOULDBLOCK",
"ERR_USE",
"ERR_ISCONN",
"ERR_ABRT",
"ERR_RST",
"ERR_CLSD",
"ERR_CONN",
"ERR_ARG",
"ERR_IF"
};
const char *
proxy_lwip_strerr(err_t error)
{
static char buf[32];
int e = -error;
if (0 < e || e < (int)__arraycount(lwiperr)) {
return lwiperr[e];
}
else {
RTStrPrintf(buf, sizeof(buf), "unknown error %d", error);
return buf;
}
}