Create three new setup_tun() library functions: openconnect_setup_tun_device - create a tun device openconnect_setup_tun_script - pass traffic through a script openconnect_setup_tun_fd - pass traffic through an existing fd The latter is needed to support Android's VpnService API. In all cases, the caller no longer needs to directly manipulate vpninfo->vpnc_script or vpninfo->script_tun. The former is treated as a standard caller-allocated, library-freed string. Signed-off-by: Kevin Cernekee <cernekee at gmail.com> --- libopenconnect.map.in | 3 ++ library.c | 2 + main.c | 18 ++++++--- openconnect-internal.h | 3 +- openconnect.h | 10 +++++ tun.c | 100 +++++++++++++++++++++++++++--------------------- 6 files changed, 85 insertions(+), 51 deletions(-) diff --git a/libopenconnect.map.in b/libopenconnect.map.in index a530324..72dbd84 100644 --- a/libopenconnect.map.in +++ b/libopenconnect.map.in @@ -47,6 +47,9 @@ OPENCONNECT_2.3 { global: openconnect_setup_cancel_pipe; openconnect_mainloop; + openconnect_setup_tun_device; + openconnect_setup_tun_script; + openconnect_setup_tun_fd; } OPENCONNECT_2.2; OPENCONNECT_PRIVATE { diff --git a/library.c b/library.c index 86c3df8..95afe9c 100644 --- a/library.c +++ b/library.c @@ -122,6 +122,8 @@ void openconnect_vpninfo_free(struct openconnect_info *vpninfo) free(vpninfo->redirect_url); free(vpninfo->proxy_type); free(vpninfo->proxy); + free(vpninfo->vpnc_script); + free(vpninfo->ifname); if (vpninfo->csd_scriptname) { unlink(vpninfo->csd_scriptname); diff --git a/main.c b/main.c index 03ba644..9253b71 100644 --- a/main.c +++ b/main.c @@ -485,6 +485,8 @@ int main(int argc, char **argv) int use_syslog = 0; char *urlpath = NULL; char *proxy = getenv("https_proxy"); + int script_tun = 0; + char *vpnc_script = NULL, *ifname = NULL; int autoproxy = 0; uid_t uid = getuid(); int opt; @@ -532,7 +534,6 @@ int main(int argc, char **argv) vpninfo->process_auth_form = process_auth_form; vpninfo->cbdata = vpninfo; vpninfo->cert_expire_warning = 60 * 86400; - vpninfo->vpnc_script = DEFAULT_VPNCSCRIPT; vpninfo->cancel_fd = -1; vpninfo->xmlpost = 1; @@ -637,7 +638,7 @@ int main(int argc, char **argv) case 'h': usage(); case 'i': - vpninfo->ifname = keep_config_arg(); + ifname = xstrdup(config_arg); break; case 'l': use_syslog = 1; @@ -681,10 +682,10 @@ int main(int argc, char **argv) nocertcheck = 1; break; case 's': - vpninfo->vpnc_script = keep_config_arg(); + vpnc_script = xstrdup(config_arg); break; case 'S': - vpninfo->script_tun = 1; + script_tun = 1; break; case 'u': vpninfo->username = keep_config_arg(); @@ -899,7 +900,14 @@ int main(int argc, char **argv) exit(1); } - if (setup_tun(vpninfo)) { + if (!vpnc_script) + vpnc_script = xstrdup(DEFAULT_VPNCSCRIPT); + if (script_tun) { + if (openconnect_setup_tun_script(vpninfo, vpnc_script)) { + fprintf(stderr, _("Set up tun script failed\n")); + exit(1); + } + } else if (openconnect_setup_tun_device(vpninfo, vpnc_script, ifname)) { fprintf(stderr, _("Set up tun device failed\n")); exit(1); } diff --git a/openconnect-internal.h b/openconnect-internal.h index 5215ea5..ebb7aa5 100644 --- a/openconnect-internal.h +++ b/openconnect-internal.h @@ -262,7 +262,7 @@ struct openconnect_info { unsigned char dtls_secret[48]; char *dtls_cipher; - const char *vpnc_script; + char *vpnc_script; int script_tun; char *ifname; @@ -396,7 +396,6 @@ static inline int cancel_fd_check(struct openconnect_info *vpninfo) /****************************************************************************/ /* tun.c */ -int setup_tun(struct openconnect_info *vpninfo); int tun_mainloop(struct openconnect_info *vpninfo, int *timeout); void shutdown_tun(struct openconnect_info *vpninfo); int script_config_tun(struct openconnect_info *vpninfo, const char *reason); diff --git a/openconnect.h b/openconnect.h index 6297e4a..5641656 100644 --- a/openconnect.h +++ b/openconnect.h @@ -224,6 +224,16 @@ int openconnect_setup_cancel_pipe(struct openconnect_info *vpninfo); const char *openconnect_get_version(void); +/* Create a tun device through the OS kernel (typical use case). Both + strings are optional and can be NULL if desired. */ +int openconnect_setup_tun_device(struct openconnect_info *vpninfo, char *vpnc_script, char *ifname); + +/* Pass traffic to a script program (no tun device). */ +int openconnect_setup_tun_script(struct openconnect_info *vpninfo, char *tun_script); + +/* Caller will provide a file descriptor for the tunnel traffic. */ +int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd); + /* Start the main loop; exits if data is received on cancel_fd or the remote site aborts. */ int openconnect_mainloop(struct openconnect_info *vpninfo); diff --git a/tun.c b/tun.c index 0db7c43..e4f7a5e 100644 --- a/tun.c +++ b/tun.c @@ -631,64 +631,76 @@ static int os_setup_tun(struct openconnect_info *vpninfo) return tun_fd; } -/* Set up a tuntap device. */ -int setup_tun(struct openconnect_info *vpninfo) +int openconnect_setup_tun_fd(struct openconnect_info *vpninfo, int tun_fd) { - int tun_fd; + fcntl(tun_fd, F_SETFD, FD_CLOEXEC); - set_script_env(vpninfo); + vpninfo->tun_fd = tun_fd; - if (vpninfo->script_tun) { - pid_t child; - int fds[2]; + if (vpninfo->select_nfds <= tun_fd) + vpninfo->select_nfds = tun_fd + 1; - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { - perror(_("socketpair")); - exit(1); - } - tun_fd = fds[0]; - child = fork(); - if (child < 0) { - perror(_("fork")); - exit(1); - } else if (!child) { - if (setpgid(0, getpid()) < 0) - perror(_("setpgid")); - close(tun_fd); - setenv_int("VPNFD", fds[1]); - execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL); - perror(_("execl")); - exit(1); - } - close(fds[1]); - vpninfo->script_tun = child; - vpninfo->ifname = strdup(_("(script)")); - } else { - script_config_tun(vpninfo, "pre-init"); + FD_SET(tun_fd, &vpninfo->select_rfds); - tun_fd = os_setup_tun(vpninfo); - if (tun_fd < 0) - return tun_fd; + fcntl(vpninfo->tun_fd, F_SETFL, fcntl(vpninfo->tun_fd, F_GETFL) | O_NONBLOCK); - setenv("TUNDEV", vpninfo->ifname, 1); - script_config_tun(vpninfo, "connect"); + return 0; +} + +int openconnect_setup_tun_script(struct openconnect_info *vpninfo, char *tun_script) +{ + pid_t child; + int fds[2]; - /* Ancient vpnc-scripts might not get this right */ - set_tun_mtu(vpninfo); + vpninfo->vpnc_script = tun_script; + vpninfo->script_tun = 1; + + set_script_env(vpninfo); + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { + perror(_("socketpair")); + exit(1); + } + child = fork(); + if (child < 0) { + perror(_("fork")); + exit(1); + } else if (!child) { + if (setpgid(0, getpid()) < 0) + perror(_("setpgid")); + close(fds[0]); + setenv_int("VPNFD", fds[1]); + execl("/bin/sh", "/bin/sh", "-c", vpninfo->vpnc_script, NULL); + perror(_("execl")); + exit(1); } + close(fds[1]); + vpninfo->script_tun = child; + vpninfo->ifname = strdup(_("(script)")); - fcntl(tun_fd, F_SETFD, FD_CLOEXEC); + return openconnect_setup_tun_fd(vpninfo, fds[0]); +} - vpninfo->tun_fd = tun_fd; +int openconnect_setup_tun_device(struct openconnect_info *vpninfo, char *vpnc_script, char *ifname) +{ + int tun_fd; - if (vpninfo->select_nfds <= tun_fd) - vpninfo->select_nfds = tun_fd + 1; + vpninfo->vpnc_script = vpnc_script; + vpninfo->ifname = ifname; - FD_SET(tun_fd, &vpninfo->select_rfds); + set_script_env(vpninfo); + script_config_tun(vpninfo, "pre-init"); - fcntl(vpninfo->tun_fd, F_SETFL, fcntl(vpninfo->tun_fd, F_GETFL) | O_NONBLOCK); + tun_fd = os_setup_tun(vpninfo); + if (tun_fd < 0) + return tun_fd; - return 0; + setenv("TUNDEV", vpninfo->ifname, 1); + script_config_tun(vpninfo, "connect"); + + /* Ancient vpnc-scripts might not get this right */ + set_tun_mtu(vpninfo); + + return openconnect_setup_tun_fd(vpninfo, tun_fd); } static struct pkt *out_pkt; -- 1.7.9.5