Added --port-share option for allowing OpenVPN and HTTPS

server to share the same port number.


git-svn-id: http://svn.openvpn.net/projects/openvpn/branches/BETA21/openvpn@893 e7ae566f-a301-0410-adde-c780ea21d3b5
This commit is contained in:
james 2006-02-16 18:12:24 +00:00
parent 34a507c9ab
commit 6add6b2fe7
24 changed files with 1122 additions and 42 deletions

View File

@ -3,8 +3,10 @@ Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
$Id$
2006.xx.xx -- Version 2.1-beta9
2006.02.16 -- Version 2.1-beta9
* Added --port-share option for allowing OpenVPN and HTTPS
server to share the same port number.
* Added --management-client option to connect as a client
to management GUI app rather than be connected to as a
server.

View File

@ -79,6 +79,7 @@ openvpn_SOURCES = \
pool.c pool.h \
proto.c proto.h \
proxy.c proxy.h \
ps.c ps.h \
push.c push.h \
reliable.c reliable.h \
route.c route.h \

View File

@ -25,7 +25,7 @@ dnl Process this file with autoconf to produce a configure script.
AC_PREREQ(2.50)
AC_INIT([OpenVPN], [2.1_beta8b], [openvpn-users@lists.sourceforge.net], [openvpn])
AC_INIT([OpenVPN], [2.1_beta8c], [openvpn-users@lists.sourceforge.net], [openvpn])
AM_CONFIG_HEADER(config.h)
AC_CONFIG_SRCDIR(syshead.h)
@ -101,6 +101,12 @@ AC_ARG_ENABLE(multihome,
[MULTIHOME="yes"]
)
AC_ARG_ENABLE(port-share,
[ --disable-port-share Disable TCP server port-share support (--port-share)],
[PORT_SHARE="$enableval"],
[PORT_SHARE="yes"]
)
AC_ARG_ENABLE(debug,
[ --disable-debug Disable debugging support (disable gremlin and verb 7+ messages)],
[DEBUG="$enableval"],
@ -636,6 +642,11 @@ if test "$FRAGMENT" = "yes"; then
AC_DEFINE(ENABLE_FRAGMENT, 1, [Enable internal fragmentation support])
fi
dnl enable --port-share
if test "$PORT_SHARE" = "yes"; then
AC_DEFINE(ENABLE_PORT_SHARE, 1, [Enable TCP Server port sharing])
fi
dnl enable strict compiler warnings
if test "$STRICT" = "yes"; then
CFLAGS="$CFLAGS -Wall -Wpointer-arith -Wsign-compare -Wno-unused-parameter -Wno-unused-function"

View File

@ -93,6 +93,7 @@
#define D_SCHED_EXIT LOGLEV(3, 42, 0) /* show arming of scheduled exit */
#define D_ROUTE_QUOTA LOGLEV(3, 43, 0) /* show route quota exceeded messages */
#define D_OSBUF LOGLEV(3, 44, 0) /* show socket/tun/tap buffer sizes */
#define D_PS_PROXY LOGLEV(3, 45, 0) /* messages related to --port-share option */
#define D_SHOW_PARMS LOGLEV(4, 50, 0) /* show all parameters on program initiation */
#define D_SHOW_OCC LOGLEV(4, 51, 0) /* show options compatibility string */
@ -132,6 +133,7 @@
#define D_ALIGN_DEBUG LOGLEV(7, 70, M_DEBUG) /* show verbose struct alignment info */
#define D_PACKET_TRUNC_DEBUG LOGLEV(7, 70, M_DEBUG) /* PACKET_TRUNCATION_CHECK verbose */
#define D_PING LOGLEV(7, 70, M_DEBUG) /* PING send/receive messages */
#define D_PS_PROXY_DEBUG LOGLEV(7, 70, M_DEBUG) /* port share proxy debug */
#define D_HANDSHAKE_VERBOSE LOGLEV(8, 70, M_DEBUG) /* show detailed description of each handshake */
#define D_TLS_DEBUG_MED LOGLEV(8, 70, M_DEBUG) /* limited info from tls_session routines */

40
error.c
View File

@ -41,6 +41,7 @@
#include "perf.h"
#include "status.h"
#include "integer.h"
#include "ps.h"
#ifdef USE_CRYPTO
#include <openssl/err.h>
@ -88,6 +89,15 @@ static char *pgmname_syslog; /* GLOBAL */
/* If non-null, messages should be written here (used for debugging only) */
static FILE *msgfp; /* GLOBAL */
/* If true, we forked from main OpenVPN process */
static bool forked; /* GLOBAL */
void
msg_forked (void)
{
forked = true;
}
bool
set_debug_level (const int level, const unsigned int flags)
{
@ -270,21 +280,22 @@ void x_msg (const unsigned int flags, const char *format, ...)
prefix_sep = prefix = "";
/* virtual output capability used to copy output to management subsystem */
{
const struct virtual_output *vo = msg_get_virtual_output ();
if (vo)
{
openvpn_snprintf (m2, ERR_BUF_SIZE, "%s%s%s",
prefix,
prefix_sep,
m1);
virtual_output_print (vo, flags, m2);
}
}
if (!forked)
{
const struct virtual_output *vo = msg_get_virtual_output ();
if (vo)
{
openvpn_snprintf (m2, ERR_BUF_SIZE, "%s%s%s",
prefix,
prefix_sep,
m1);
virtual_output_print (vo, flags, m2);
}
}
if (!(flags & M_MSG_VIRT_OUT))
{
if (use_syslog && !std_redir)
if (use_syslog && !std_redir && !forked)
{
#if SYSLOG_CAPABILITY
syslog (level, "%s%s%s",
@ -674,6 +685,11 @@ openvpn_exit (const int status)
plugin_abort ();
#endif
#if PORT_SHARE
if (port_share)
port_share_abort (port_share);
#endif
#ifdef ABORT_ON_ERROR
if (status == OPENVPN_EXIT_STATUS_ERROR)
abort ();

View File

@ -196,7 +196,7 @@ FILE *msg_fp(void);
void assert_failed (const char *filename, int line);
#ifdef ENABLE_DEBUG
void crash (void); // force a segfault (debugging only)
void crash (void); /* force a segfault (debugging only) */
#endif
/* Inline functions */
@ -207,6 +207,9 @@ check_debug_level (unsigned int level)
return (level & M_DEBUG_LEVEL) <= x_debug_level;
}
/* Call if we forked */
void msg_forked (void);
/* syslog output */
void open_syslog (const char *pgmname, bool stdio_to_null);

View File

@ -33,9 +33,9 @@
* rwflags passed to event_ctl and returned by
* struct event_set_return.
*/
#define EVENT_UNDEF 4
#define EVENT_READ (1<<0)
#define EVENT_WRITE (1<<1)
/*
* Initialization flags passed to event_set_init
*/
@ -98,7 +98,8 @@ struct event_set *event_set_init (int *maxevents, unsigned int flags);
static inline void
event_free (struct event_set *es)
{
(*es->func.free)(es);
if (es)
(*es->func.free)(es);
}
static inline void

View File

@ -36,25 +36,43 @@
#include "memdbg.h"
/* Set a file descriptor to non-blocking */
void
set_nonblock (int fd)
bool
set_nonblock_action (int fd)
{
#ifdef WIN32
u_long arg = 1;
if (ioctlsocket (fd, FIONBIO, &arg))
msg (M_SOCKERR, "Set socket to non-blocking mode failed");
return false;
#else
if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0)
msg (M_ERR, "Set file descriptor to non-blocking mode failed");
return false;
#endif
return true;
}
/* Set a file descriptor to not be passed across execs */
bool
set_cloexec_action (int fd)
{
#ifndef WIN32
if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0)
return false;
#endif
return true;
}
/* Set a file descriptor to non-blocking */
void
set_nonblock (int fd)
{
if (!set_nonblock_action (fd))
msg (M_SOCKERR, "Set socket to non-blocking mode failed");
}
/* Set a file descriptor to not be passed across execs */
void
set_cloexec (int fd)
{
#ifndef WIN32
if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0)
if (!set_cloexec_action (fd))
msg (M_ERR, "Set FD_CLOEXEC flag on file descriptor failed");
#endif
}

View File

@ -22,5 +22,10 @@
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "basic.h"
bool set_nonblock_action (int fd);
bool set_cloexec_action (int fd);
void set_nonblock (int fd);
void set_cloexec (int fd);

View File

@ -36,6 +36,7 @@
#include "gremlin.h"
#include "mss.h"
#include "event.h"
#include "ps.h"
#include "memdbg.h"
@ -640,18 +641,31 @@ read_incoming_link (struct context *c)
if (socket_connection_reset (c->c2.link_socket, status))
{
/* received a disconnect from a connection-oriented protocol */
if (c->options.inetd)
#if PORT_SHARE
if (port_share && socket_foreign_protocol_detected (c->c2.link_socket))
{
const struct buffer *fbuf = socket_foreign_protocol_head (c->c2.link_socket);
const int sd = socket_foreign_protocol_sd (c->c2.link_socket);
port_share_redirect (port_share, fbuf, sd);
c->sig->signal_received = SIGTERM;
msg (D_STREAM_ERRORS, "Connection reset, inetd/xinetd exit [%d]", status);
c->sig->signal_text = "port-share-redirect";
}
else
{
c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- TCP connection reset */
msg (D_STREAM_ERRORS, "Connection reset, restarting [%d]", status);
}
c->sig->signal_text = "connection-reset";
#endif
{
/* received a disconnect from a connection-oriented protocol */
if (c->options.inetd)
{
c->sig->signal_received = SIGTERM;
msg (D_STREAM_ERRORS, "Connection reset, inetd/xinetd exit [%d]", status);
}
else
{
c->sig->signal_received = SIGUSR1; /* SOFT-SIGUSR1 -- TCP connection reset */
msg (D_STREAM_ERRORS, "Connection reset, restarting [%d]", status);
}
c->sig->signal_text = "connection-reset";
}
perf_pop ();
return;
}

42
init.c
View File

@ -39,6 +39,7 @@
#include "pool.h"
#include "gremlin.h"
#include "pkcs11.h"
#include "ps.h"
#include "memdbg.h"
@ -191,6 +192,32 @@ context_gc_free (struct context *c)
gc_free (&c->gc);
}
#if PORT_SHARE
static void
close_port_share (void)
{
if (port_share)
{
port_share_close (port_share);
port_share = NULL;
}
}
static void
init_port_share (struct context *c)
{
if (!port_share && (c->options.port_share_host && c->options.port_share_port))
{
port_share = port_share_open (c->options.port_share_host,
c->options.port_share_port);
if (port_share == NULL)
msg (M_FATAL, "Fatal error: Port sharing failed");
}
}
#endif
bool
init_static (void)
{
@ -274,6 +301,10 @@ uninit_static (void)
pkcs11_terminate ();
#endif
#if PORT_SHARE
close_port_share ();
#endif
#if defined(MEASURE_TLS_HANDSHAKE_STATS) && defined(USE_CRYPTO) && defined(USE_SSL)
show_tls_performance_stats ();
#endif
@ -1490,6 +1521,11 @@ do_init_crypto_tls (struct context *c, const unsigned int flags)
to.renegotiate_seconds = options->renegotiate_seconds;
to.single_session = options->single_session;
/* should we not xmit any packets until we get an initial
response from client? */
if (to.server && options->proto == PROTO_TCPv4_SERVER)
to.xmit_hold = true;
#ifdef ENABLE_OCC
to.disable_occ = !options->occ;
#endif
@ -2659,6 +2695,12 @@ init_instance (struct context *c, const struct env_set *env, const unsigned int
open_plugins (c, false, OPENVPN_PLUGIN_INIT_POST_UID_CHANGE);
#endif
#if PORT_SHARE
/* share OpenVPN port with foreign (such as HTTPS) server */
if (c->first_time && (c->mode == CM_P2P || c->mode == CM_TOP))
init_port_share (c);
#endif
/* Check for signals */
if (IS_SIG (c))
goto sig;

View File

@ -217,6 +217,7 @@ openvpn \- secure IP tunnel daemon.
[\ \fB\-\-pkcs12\fR\ \fIfile\fR\ ]
[\ \fB\-\-plugin\fR\ \fImodule\-pathname\ init\-string\fR\ ]
[\ \fB\-\-port\fR\ \fIport\fR\ ]
[\ \fB\-\-port\-share\fR\ \fIhost\ port\fR\ ]
[\ \fB\-\-proto\fR\ \fIp\fR\ ]
[\ \fB\-\-pull\fR\ ]
[\ \fB\-\-push\-reset\fR\ ]
@ -2971,6 +2972,23 @@ authentication, use
the authenticated username as the common name,
rather than the common name from the client cert.
.\"*********************************************************
.TP
.B --port-share host port
When run in TCP server mode, share the OpenVPN port with
another application, such as an HTTPS server. If OpenVPN
senses a connection to its port which is using a non-OpenVPN
protocol, it will proxy the connection to the server at
.B host:port.
Currently only designed to work with HTTPS,
though it would be theoretically possible to extend to
other protocols such as ssh.
Currently only implemented
on Linux, though porting to BSDs should be straightforward.
The reason for the non-portability is that the current implementation
uses sendmsg and recvmsg for passing file descriptors between
processes.
.\"*********************************************************
.SS Client Mode
Use client mode when connecting to an OpenVPN server
which has

View File

@ -192,7 +192,7 @@ main (int argc, char *argv[])
if (!open_management (&c))
break;
#endif
/* set certain options as environmental variables */
setenv_settings (c.es, &c.options);

View File

@ -363,6 +363,10 @@ static const char usage_message[] =
"--connect-freq n s : Allow a maximum of n new connections per s seconds.\n"
"--max-clients n : Allow a maximum of n simultaneously connected clients.\n"
"--max-routes-per-client n : Allow a maximum of n internal routes per client.\n"
#if PORT_SHARE
"--port-share host port : When run in TCP mode, proxy incoming HTTPS sessions\n"
" to a web server at host:port.\n"
#endif
#endif
"\n"
"Client options (when connecting to a multi-client server):\n"
@ -918,6 +922,10 @@ show_p2mp_parms (const struct options *o)
SHOW_BOOL (username_as_common_name)
SHOW_STR (auth_user_pass_verify_script);
SHOW_BOOL (auth_user_pass_verify_script_via_file);
#if PORT_SHARE
SHOW_STR (port_share_host);
SHOW_INT (port_share_port);
#endif
#endif /* P2MP_SERVER */
SHOW_BOOL (client);
@ -1594,6 +1602,10 @@ options_postprocess (struct options *options, bool first_time)
msg (M_USAGE, "--pull cannot be used with --mode server");
if (!(options->proto == PROTO_UDPv4 || options->proto == PROTO_TCPv4_SERVER))
msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server");
#if PORT_SHARE
if ((options->port_share_host || options->port_share_port) && options->proto != PROTO_TCPv4_SERVER)
msg (M_USAGE, "--port-share only works in TCP server mode (--proto tcp-server)");
#endif
if (!options->tls_server)
msg (M_USAGE, "--mode server requires --tls-server");
if (options->remote_list)
@ -1682,6 +1694,11 @@ options_postprocess (struct options *options, bool first_time)
msg (M_USAGE, "--username-as-common-name requires --mode server");
if (options->auth_user_pass_verify_script)
msg (M_USAGE, "--auth-user-pass-verify requires --mode server");
#if PORT_SHARE
if (options->port_share_host || options->port_share_port)
msg (M_USAGE, "--port-share requires TCP server mode (--mode server --proto tcp-server)");
#endif
}
#endif /* P2MP_SERVER */
@ -4234,6 +4251,23 @@ add_option (struct options *options,
msg (msglevel, "--tcp-queue-limit parameter must be > 0");
options->tcp_queue_limit = tcp_queue_limit;
}
#if PORT_SHARE
else if (streq (p[0], "port-share") && p[1] && p[2])
{
int port;
VERIFY_PERMISSION (OPT_P_GENERAL);
port = atoi (p[2]);
if (!legal_ipv4_port (port))
{
msg (msglevel, "port number associated with --port-share directive is out of range");
goto err;
}
options->port_share_host = p[1];
options->port_share_port = port;
}
#endif
else if (streq (p[0], "client-to-client"))
{
VERIFY_PERMISSION (OPT_P_GENERAL);

View File

@ -344,6 +344,10 @@ struct options
bool username_as_common_name;
const char *auth_user_pass_verify_script;
bool auth_user_pass_verify_script_via_file;
#if PORT_SHARE
char *port_share_host;
int port_share_port;
#endif
#endif
bool client;

783
ps.c Normal file
View File

@ -0,0 +1,783 @@
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifdef WIN32
#include "config-win32.h"
#else
#include "config.h"
#endif
#include "syshead.h"
#if PORT_SHARE
#include "event.h"
#include "socket.h"
#include "fdmisc.h"
#include "ps.h"
#include "memdbg.h"
struct port_share *port_share = NULL; /* GLOBAL */
/* size of i/o buffers */
#define PROXY_CONNECTION_BUFFER_SIZE 1500
/* Command codes for foreground -> background communication */
#define COMMAND_REDIRECT 10
#define COMMAND_EXIT 11
/* Response codes for background -> foreground communication */
#define RESPONSE_INIT_SUCCEEDED 20
#define RESPONSE_INIT_FAILED 21
/* A foreign (non-OpenVPN) connection we are proxying,
usually HTTPS */
struct proxy_connection {
bool defined;
struct proxy_connection *next;
struct proxy_connection *counterpart;
struct buffer buf;
bool buffer_initial;
int rwflags;
int sd;
};
/* used for passing fds between processes */
union fdmsg {
struct cmsghdr h;
char buf[CMSG_SPACE(sizeof(socket_descriptor_t))];
};
#if 0
static const char *
headc (const struct buffer *buf)
{
static char foo[16];
strncpy (foo, BSTR(buf), 15);
foo[15] = 0;
return foo;
}
#endif
static void
close_socket_if_defined (const socket_descriptor_t sd)
{
if (socket_defined (sd))
openvpn_close_socket (sd);
}
/*
* Close most of parent's fds.
* Keep stdin/stdout/stderr, plus one
* other fd which is presumed to be
* our pipe back to parent.
* Admittedly, a bit of a kludge,
* but posix doesn't give us a kind
* of FD_CLOEXEC which will stop
* fds from crossing a fork().
*/
static void
close_fds_except (int keep)
{
socket_descriptor_t i;
closelog ();
for (i = 3; i <= 100; ++i)
{
if (i != keep)
openvpn_close_socket (i);
}
}
/*
* Usually we ignore signals, because our parent will
* deal with them.
*/
static void
set_signals (void)
{
signal (SIGTERM, SIG_DFL);
signal (SIGINT, SIG_IGN);
signal (SIGHUP, SIG_IGN);
signal (SIGUSR1, SIG_IGN);
signal (SIGUSR2, SIG_IGN);
signal (SIGPIPE, SIG_IGN);
}
/*
* Socket read/write functions.
*/
static int
recv_control (const socket_descriptor_t fd)
{
unsigned char c;
const ssize_t size = read (fd, &c, sizeof (c));
if (size == sizeof (c))
return c;
else
{
return -1;
}
}
static int
send_control (const socket_descriptor_t fd, int code)
{
unsigned char c = (unsigned char) code;
const ssize_t size = write (fd, &c, sizeof (c));
if (size == sizeof (c))
return (int) size;
else
return -1;
}
static void
port_share_sendmsg (const socket_descriptor_t sd,
const char command,
const struct buffer *head,
const socket_descriptor_t sd_send)
{
if (socket_defined (sd))
{
struct msghdr mesg;
union fdmsg cmsg;
struct cmsghdr* h;
struct iovec iov[2];
socket_descriptor_t sd_null[2] = { SOCKET_UNDEFINED, SOCKET_UNDEFINED };
char cmd;
ssize_t status;
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: sendmsg sd=%d len=%d",
sd_send,
head ? BLEN(head) : -1);
CLEAR (mesg);
cmd = command;
iov[0].iov_base = &cmd;
iov[0].iov_len = sizeof (cmd);
mesg.msg_iovlen = 1;
if (head)
{
iov[1].iov_base = BPTR (head);
iov[1].iov_len = BLEN (head);
mesg.msg_iovlen = 2;
}
mesg.msg_iov = iov;
mesg.msg_control = cmsg.buf;
mesg.msg_controllen = sizeof (union fdmsg);
mesg.msg_flags = 0;
h = CMSG_FIRSTHDR(&mesg);
h->cmsg_level = SOL_SOCKET;
h->cmsg_type = SCM_RIGHTS;
h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t));
if (socket_defined (sd_send))
{
*((socket_descriptor_t*)CMSG_DATA(h)) = sd_send;
}
else
{
socketpair (PF_UNIX, SOCK_DGRAM, 0, sd_null);
*((socket_descriptor_t*)CMSG_DATA(h)) = sd_null[0];
}
status = sendmsg (sd, &mesg, MSG_NOSIGNAL);
if (status == -1)
msg (M_WARN, "PORT SHARE: sendmsg failed (unable to communicate with background process)");
close_socket_if_defined (sd_null[0]);
close_socket_if_defined (sd_null[1]);
}
}
static int
pc_list_len (struct proxy_connection *pc)
{
int count = 0;
while (pc)
{
++count;
pc = pc->next;
}
return count;
}
/* mark a proxy entry and its counterpart for close */
static void
proxy_entry_mark_for_close (struct proxy_connection *pc, struct event_set *es)
{
if (pc->defined)
{
struct proxy_connection *cp = pc->counterpart;
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: delete sd=%d", pc->sd);
if (socket_defined (pc->sd))
{
if (es)
event_del (es, pc->sd);
openvpn_close_socket (pc->sd);
pc->sd = SOCKET_UNDEFINED;
}
free_buf (&pc->buf);
pc->buffer_initial = false;
pc->rwflags = 0;
pc->counterpart = NULL;
pc->defined = false;
if (cp && cp->defined && cp->counterpart == pc)
proxy_entry_mark_for_close (cp, es);
}
}
static void
proxy_list_housekeeping (struct proxy_connection **list)
{
if (list)
{
struct proxy_connection *prev = NULL;
struct proxy_connection *pc = *list;
while (pc)
{
struct proxy_connection *next = pc->next;
if (!pc->defined)
{
free (pc);
if (prev)
prev->next = next;
else
*list = next;
}
else
prev = pc;
pc = next;
}
}
}
static void
proxy_list_close (struct proxy_connection **list)
{
if (list)
{
struct proxy_connection *pc = *list;
while (pc)
{
proxy_entry_mark_for_close (pc, NULL);
pc = pc->next;
}
proxy_list_housekeeping (list);
}
}
static void
sock_addr_set (struct openvpn_sockaddr *osaddr,
const in_addr_t addr,
const int port)
{
CLEAR (*osaddr);
osaddr->sa.sin_family = AF_INET;
osaddr->sa.sin_addr.s_addr = htonl (addr);
osaddr->sa.sin_port = htons (port);
}
static inline void
proxy_connection_io_requeue (struct proxy_connection *pc, const int rwflags_new, struct event_set *es)
{
if (pc->rwflags != rwflags_new)
{
event_ctl (es, pc->sd, rwflags_new, (void*)pc);
pc->rwflags = rwflags_new;
}
}
static bool
proxy_entry_new (struct proxy_connection **list,
struct event_set *es,
const in_addr_t server_addr,
const int server_port,
const socket_descriptor_t sd_client,
struct buffer *initial_data)
{
struct openvpn_sockaddr osaddr;
socket_descriptor_t sd_server;
int status;
struct proxy_connection *pc;
struct proxy_connection *cp;
/* connect to port share server */
sock_addr_set (&osaddr, server_addr, server_port);
sd_server = create_socket_tcp ();
status = openvpn_connect (sd_server, &osaddr, 5, NULL);
if (status)
{
msg (M_WARN, "PORT SHARE PROXY: connect to port-share server failed");
openvpn_close_socket (sd_server);
return false;
}
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: connect to port-share server succeeded");
set_nonblock (sd_client);
set_nonblock (sd_server);
/* allocate 2 new proxy_connection objects */
ALLOC_OBJ_CLEAR (pc, struct proxy_connection);
ALLOC_OBJ_CLEAR (cp, struct proxy_connection);
/* client object */
pc->defined = true;
pc->next = cp;
pc->counterpart = cp;
pc->buf = *initial_data;
pc->buffer_initial = true;
pc->rwflags = EVENT_UNDEF;
pc->sd = sd_client;
/* server object */
cp->defined = true;
cp->next = *list;
cp->counterpart = pc;
cp->buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE);
cp->buffer_initial = false;
cp->rwflags = EVENT_UNDEF;
cp->sd = sd_server;
/* add to list */
*list = pc;
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: NEW CONNECTION [c=%d s=%d]", sd_client, sd_server);
/* set initial i/o states */
proxy_connection_io_requeue (pc, EVENT_READ, es);
proxy_connection_io_requeue (cp, EVENT_READ|EVENT_WRITE, es);
return true;
}
static bool
control_message_from_parent (const socket_descriptor_t sd_control,
struct proxy_connection **list,
struct event_set *es,
const in_addr_t server_addr,
const int server_port)
{
struct buffer buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE);
struct msghdr mesg;
union fdmsg cmsg;
struct cmsghdr* h;
struct iovec iov[2];
char command = 0;
ssize_t status;
int ret = true;
CLEAR (mesg);
iov[0].iov_base = &command;
iov[0].iov_len = sizeof (command);
iov[1].iov_base = BPTR (&buf);
iov[1].iov_len = BCAP (&buf);
mesg.msg_iov = iov;
mesg.msg_iovlen = 2;
mesg.msg_control = cmsg.buf;
mesg.msg_controllen = sizeof (union fdmsg);
mesg.msg_flags = 0;
h = CMSG_FIRSTHDR(&mesg);
h->cmsg_len = CMSG_LEN(sizeof(socket_descriptor_t));
h->cmsg_level = SOL_SOCKET;
h->cmsg_type = SCM_RIGHTS;
*((socket_descriptor_t*)CMSG_DATA(h)) = SOCKET_UNDEFINED;
status = recvmsg (sd_control, &mesg, MSG_NOSIGNAL);
if (status != -1)
{
if ( h == NULL
|| h->cmsg_len != CMSG_LEN(sizeof(socket_descriptor_t))
|| h->cmsg_level != SOL_SOCKET
|| h->cmsg_type != SCM_RIGHTS )
{
ret = false;
}
else
{
const socket_descriptor_t received_fd = *((socket_descriptor_t*)CMSG_DATA(h));
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: RECEIVED sd=%d", received_fd);
if (status >= 2 && command == COMMAND_REDIRECT)
{
buf.len = status - 1;
if (proxy_entry_new (list,
es,
server_addr,
server_port,
received_fd,
&buf))
{
CLEAR (buf); /* we gave the buffer to proxy_entry_new */
}
else
{
openvpn_close_socket (received_fd);
}
}
else if (status >= 1 && command == COMMAND_EXIT)
{
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: RECEIVED COMMAND_EXIT");
openvpn_close_socket (received_fd); /* null socket */
ret = false;
}
}
}
free_buf (&buf);
return ret;
}
/* proxy_connection_io_xfer return values */
#define IOSTAT_EAGAIN_ON_READ 0
#define IOSTAT_EAGAIN_ON_WRITE 1
#define IOSTAT_ERROR 2
/* forward data from pc to pc->counterpart */
static int
proxy_connection_io_xfer (struct proxy_connection *pc)
{
while (true)
{
if (!BLEN (&pc->buf))
{
/* recv data from socket */
ssize_t status = recv (pc->sd, BPTR(&pc->buf), BCAP(&pc->buf), MSG_NOSIGNAL);
if (status == -1)
{
return (errno == EAGAIN) ? IOSTAT_EAGAIN_ON_READ : IOSTAT_ERROR;
}
else
{
if (!status)
return IOSTAT_ERROR;
pc->buf.len = status;
}
}
if (BLEN (&pc->buf))
{
/* send data to counterpart socket */
ssize_t status = send (pc->counterpart->sd, BPTR(&pc->buf), BLEN(&pc->buf), MSG_NOSIGNAL);
if (status == -1)
{
const int e = errno;
return (e == EAGAIN) ? IOSTAT_EAGAIN_ON_WRITE : IOSTAT_ERROR;
}
else
{
if (status != pc->buf.len)
return IOSTAT_ERROR;
pc->buf.len = 0;
}
/* successful send */
if (pc->buffer_initial)
{
free_buf (&pc->buf);
pc->buf = alloc_buf (PROXY_CONNECTION_BUFFER_SIZE);
pc->buffer_initial = false;
}
}
}
return IOSTAT_ERROR;
}
static inline bool
proxy_connection_io_status (const int status, int *rwflags_pc, int *rwflags_cp)
{
switch (status)
{
case IOSTAT_EAGAIN_ON_READ:
*rwflags_pc |= EVENT_READ;
*rwflags_cp &= ~EVENT_WRITE;
return true;
case IOSTAT_EAGAIN_ON_WRITE:
*rwflags_pc &= ~EVENT_READ;
*rwflags_cp |= EVENT_WRITE;
return true;
default:
return false;
}
}
static bool
proxy_connection_io_dispatch (struct proxy_connection *pc,
const int rwflags,
struct event_set *es)
{
struct proxy_connection *cp = pc->counterpart;
int status;
int rwflags_pc = pc->rwflags;
int rwflags_cp = cp->rwflags;
if (rwflags & EVENT_READ)
{
status = proxy_connection_io_xfer (pc);
if (!proxy_connection_io_status (status, &rwflags_pc, &rwflags_cp))
goto bad;
}
if (rwflags & EVENT_WRITE)
{
status = proxy_connection_io_xfer (cp);
if (!proxy_connection_io_status (status, &rwflags_cp, &rwflags_pc))
goto bad;
}
proxy_connection_io_requeue (pc, rwflags_pc, es);
proxy_connection_io_requeue (cp, rwflags_cp, es);
return true;
bad:
proxy_entry_mark_for_close (pc, es);
return false;
}
static void
port_share_proxy (const in_addr_t hostaddr, const int port, const socket_descriptor_t sd_control)
{
if (send_control (sd_control, RESPONSE_INIT_SUCCEEDED) >= 0)
{
void *sd_control_marker = (void *)1;
int maxevents = 256;
struct event_set *es;
struct event_set_return esr[64];
struct proxy_connection *list = NULL;
time_t last_housekeeping = 0;
msg (D_PS_PROXY, "PORT SHARE PROXY: proxy starting");
es = event_set_init (&maxevents, 0);
event_ctl (es, sd_control, EVENT_READ, sd_control_marker);
while (true)
{
int n_events;
struct timeval tv;
time_t current;
tv.tv_sec = 10;
tv.tv_usec = 0;
n_events = event_wait (es, &tv, esr, SIZE(esr));
current = time(NULL);
if (n_events > 0)
{
int i;
for (i = 0; i < n_events; ++i)
{
const struct event_set_return *e = &esr[i];
if (e->arg == sd_control_marker)
{
if (!control_message_from_parent (sd_control, &list, es, hostaddr, port))
goto done;
}
else
{
struct proxy_connection *pc = (struct proxy_connection *)e->arg;
if (pc->defined)
proxy_connection_io_dispatch (pc, e->rwflags, es);
}
}
}
else if (n_events < 0)
{
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE PROXY: event_wait failed");
}
if (current > last_housekeeping)
{
proxy_list_housekeeping (&list);
last_housekeeping = current;
}
}
done:
proxy_list_close (&list);
event_free (es);
}
msg (D_PS_PROXY, "PORT SHARE PROXY: proxy exiting");
}
struct port_share *
port_share_open (const char *host, const int port)
{
pid_t pid;
socket_descriptor_t fd[2];
in_addr_t hostaddr;
struct port_share *ps;
ALLOC_OBJ_CLEAR (ps, struct port_share);
/*
* Get host's IP address
*/
hostaddr = getaddr (GETADDR_RESOLVE|GETADDR_HOST_ORDER|GETADDR_FATAL, host, 0, NULL, NULL);
/*
* Make a socket for foreground and background processes
* to communicate.
*/
if (socketpair (PF_UNIX, SOCK_DGRAM, 0, fd) == -1)
{
msg (M_WARN, "PORT SHARE: socketpair call failed");
goto error;
}
/*
* Fork off background proxy process.
*/
pid = fork ();
if (pid)
{
int status;
/*
* Foreground Process
*/
ps->background_pid = pid;
/* close our copy of child's socket */
openvpn_close_socket (fd[1]);
/* don't let future subprocesses inherit child socket */
set_cloexec (fd[0]);
/* wait for background child process to initialize */
status = recv_control (fd[0]);
if (status == RESPONSE_INIT_SUCCEEDED)
{
ps->foreground_fd = fd[0];
return ps;
}
}
else
{
/*
* Background Process
*/
/* Ignore most signals (the parent will receive them) */
set_signals ();
/* Let msg know that we forked */
msg_forked ();
/* close all parent fds except our socket back to parent */
close_fds_except (fd[1]);
/* no blocking on control channel back to parent */
set_nonblock (fd[1]);
/* execute the event loop */
port_share_proxy (hostaddr, port, fd[1]);
openvpn_close_socket (fd[1]);
exit (0);
return 0; /* NOTREACHED */
}
error:
port_share_close (ps);
return NULL;
}
void
port_share_close (struct port_share *ps)
{
if (ps)
{
if (ps->foreground_fd >= 0)
{
/* tell background process to exit */
port_share_sendmsg (ps->foreground_fd, COMMAND_EXIT, NULL, SOCKET_UNDEFINED);
/* wait for background process to exit */
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: waiting for background process to exit");
if (ps->background_pid > 0)
waitpid (ps->background_pid, NULL, 0);
dmsg (D_PS_PROXY_DEBUG, "PORT SHARE: background process exited");
openvpn_close_socket (ps->foreground_fd);
ps->foreground_fd = -1;
}
free (ps);
}
}
void
port_share_abort (struct port_share *ps)
{
if (ps)
{
/* tell background process to exit */
if (ps->foreground_fd >= 0)
{
send_control (ps->foreground_fd, COMMAND_EXIT);
openvpn_close_socket (ps->foreground_fd);
ps->foreground_fd = -1;
}
}
}
bool
is_openvpn_protocol (const struct buffer *buf)
{
const unsigned char *p = BSTR (buf);
const int len = BLEN (buf);
if (len >= 3)
{
return p[0] == 0
&& p[1] >= 14
&& p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
}
else if (len >= 2)
{
return p[0] == 0 && p[1] >= 14;
}
else
return true;
}
void
port_share_redirect (struct port_share *ps, const struct buffer *head, socket_descriptor_t sd)
{
if (ps)
port_share_sendmsg (ps->foreground_fd, COMMAND_REDIRECT, head, sd);
}
#endif

57
ps.h Normal file
View File

@ -0,0 +1,57 @@
/*
* OpenVPN -- An application to securely tunnel IP networks
* over a single TCP/UDP port, with support for SSL/TLS-based
* session authentication and key exchange,
* packet encryption, packet authentication, and
* packet compression.
*
* Copyright (C) 2002-2005 OpenVPN Solutions LLC <info@openvpn.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (see the file COPYING included with this
* distribution); if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef PS_H
#define PS_H
#if PORT_SHARE
#include "basic.h"
#include "buffer.h"
#include "ssl.h"
typedef void (*post_fork_cleanup_func_t)(void *arg);
struct port_share {
/* Foreground's socket to background process */
socket_descriptor_t foreground_fd;
/* Process ID of background process */
pid_t background_pid;
};
extern struct port_share *port_share;
struct port_share *port_share_open (const char *host,
const int port);
void port_share_close (struct port_share *ps);
void port_share_abort (struct port_share *ps);
bool is_openvpn_protocol (const struct buffer *buf);
void port_share_redirect (struct port_share *ps, const struct buffer *head, socket_descriptor_t sd);
#endif
#endif

View File

@ -221,12 +221,13 @@ reliable_ack_print (struct buffer *buf, bool verbose, struct gc_arena *gc)
*/
void
reliable_init (struct reliable *rel, int buf_size, int offset, int array_size)
reliable_init (struct reliable *rel, int buf_size, int offset, int array_size, bool hold)
{
int i;
CLEAR (*rel);
ASSERT (array_size > 0 && array_size <= RELIABLE_CAPACITY);
rel->hold = hold;
rel->size = array_size;
rel->offset = offset;
for (i = 0; i < rel->size; ++i)
@ -465,7 +466,7 @@ reliable_can_send (const struct reliable *rel)
reliable_print_ids (rel, &gc));
gc_free (&gc);
return n_current > 0;
return n_current > 0 && !rel->hold;
}
/* return a unique point-in-time to trigger retry */
@ -530,6 +531,7 @@ reliable_schedule_now (struct reliable *rel)
{
int i;
dmsg (D_REL_DEBUG, "ACK reliable_schedule_now");
rel->hold = false;
for (i = 0; i < rel->size; ++i)
{
struct reliable_entry *e = &rel->array[i];

View File

@ -96,6 +96,7 @@ struct reliable
interval_t initial_timeout;
packet_id_type packet_id;
int offset;
bool hold; /* don't xmit until reliable_schedule_now is called */
struct reliable_entry array[RELIABLE_CAPACITY];
};
@ -108,7 +109,7 @@ reliable_set_timeout (struct reliable *rel, interval_t timeout)
rel->initial_timeout = timeout;
}
void reliable_init (struct reliable *rel, int buf_size, int offset, int array_size);
void reliable_init (struct reliable *rel, int buf_size, int offset, int array_size, bool hold);
void reliable_free (struct reliable *rel);

View File

@ -36,6 +36,7 @@
#include "misc.h"
#include "gremlin.h"
#include "plugin.h"
#include "ps.h"
#include "memdbg.h"
@ -739,11 +740,14 @@ openvpn_connect (socket_descriptor_t sd,
status = select (sd + 1, NULL, &writes, NULL, &tv);
get_signal (signal_received);
if (*signal_received)
if (signal_received)
{
status = 0;
break;
get_signal (signal_received);
if (*signal_received)
{
status = 0;
break;
}
}
if (status < 0)
{
@ -1666,6 +1670,9 @@ stream_buf_init (struct stream_buf *sb,
sb->buf_init.len = 0;
sb->residual = alloc_buf (sb->maxlen);
sb->error = false;
#if PORT_SHARE
sb->port_share_state = PS_ENABLED;
#endif
stream_buf_reset (sb);
dmsg (D_STREAM_DEBUG, "STREAM: INIT maxlen=%d", sb->maxlen);
@ -1735,6 +1742,22 @@ stream_buf_added (struct stream_buf *sb,
if (sb->len < 0 && sb->buf.len >= (int) sizeof (packet_size_type))
{
packet_size_type net_size;
#if PORT_SHARE
if (sb->port_share_state == PS_ENABLED)
{
if (!is_openvpn_protocol (&sb->buf))
{
msg (D_STREAM_ERRORS, "Non-OpenVPN protocol detected");
sb->port_share_state = PS_FOREIGN;
sb->error = true;
return false;
}
else
sb->port_share_state = PS_DISABLED;
}
#endif
ASSERT (buf_read (&sb->buf, &net_size, sizeof (net_size)));
sb->len = ntohps (net_size);

View File

@ -130,6 +130,12 @@ struct stream_buf
bool error; /* if true, fatal TCP error has occurred,
requiring that connection be restarted */
#if PORT_SHARE
# define PS_DISABLED 0
# define PS_ENABLED 1
# define PS_FOREIGN 2
int port_share_state;
#endif
};
/*
@ -540,6 +546,29 @@ link_socket_actual_match (const struct link_socket_actual *a1, const struct link
return addr_port_match (&a1->dest, &a2->dest);
}
#if PORT_SHARE
static inline bool
socket_foreign_protocol_detected (const struct link_socket *sock)
{
return link_socket_connection_oriented (sock)
&& sock->stream_buf.port_share_state == PS_FOREIGN;
}
static inline const struct buffer *
socket_foreign_protocol_head (const struct link_socket *sock)
{
return &sock->stream_buf.buf;
}
static inline int
socket_foreign_protocol_sd (const struct link_socket *sock)
{
return sock->sd;
}
#endif
static inline bool
socket_connection_reset (const struct link_socket *sock, int status)
{

6
ssl.c
View File

@ -1791,9 +1791,11 @@ key_state_init (struct tls_session *session, struct key_state *ks)
ks->plaintext_write_buf = alloc_buf (PLAINTEXT_BUFFER_SIZE);
ks->ack_write_buf = alloc_buf (BUF_SIZE (&session->opt->frame));
reliable_init (ks->send_reliable, BUF_SIZE (&session->opt->frame),
FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_SEND_BUFFERS);
FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_SEND_BUFFERS,
session->opt->xmit_hold);
reliable_init (ks->rec_reliable, BUF_SIZE (&session->opt->frame),
FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_REC_BUFFERS);
FRAME_HEADROOM (&session->opt->frame), TLS_RELIABLE_N_REC_BUFFERS,
false);
reliable_set_timeout (ks->send_reliable, session->opt->packet_timeout);
/* init packet ID tracker */

3
ssl.h
View File

@ -384,6 +384,9 @@ struct tls_options
/* true if we are a TLS server, client otherwise */
bool server;
/* if true, don't xmit until first packet from peer is received */
bool xmit_hold;
#ifdef ENABLE_OCC
/* local and remote options strings
that must match between client and server */

View File

@ -395,6 +395,15 @@ socket_defined (const socket_descriptor_t sd)
#define P2MP_SERVER 0
#endif
/*
* HTTPS port sharing capability
*/
#if defined(ENABLE_PORT_SHARE) && P2MP_SERVER && defined(SCM_RIGHTS) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)
#define PORT_SHARE 1
#else
#define PORT_SHARE 0
#endif
/*
* Do we have a plug-in capability?
*/