diff options
Diffstat (limited to 'tun.c')
-rw-r--r-- | tun.c | 3434 |
1 files changed, 3434 insertions, 0 deletions
@@ -0,0 +1,3434 @@ +/* + * 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 + */ + +/* + * Support routines for configuring and accessing TUN/TAP + * virtual network adapters. + * + * This file is based on the TUN/TAP driver interface routines + * from VTun by Maxim Krasnyansky <max_mk@yahoo.com>. + */ + +#ifdef WIN32 +#include "config-win32.h" +#else +#include "config.h" +#endif + +#include "syshead.h" + +#include "tun.h" +#include "fdmisc.h" +#include "common.h" +#include "misc.h" +#include "socket.h" +#include "manage.h" + +#include "memdbg.h" + +#ifdef TARGET_SOLARIS +static void solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual); +#endif + +bool +is_dev_type (const char *dev, const char *dev_type, const char *match_type) +{ + ASSERT (match_type); + if (!dev) + return false; + if (dev_type) + return !strcmp (dev_type, match_type); + else + return !strncmp (dev, match_type, strlen (match_type)); +} + +int +dev_type_enum (const char *dev, const char *dev_type) +{ + if (is_dev_type (dev, dev_type, "tun")) + return DEV_TYPE_TUN; + else if (is_dev_type (dev, dev_type, "tap")) + return DEV_TYPE_TAP; + else if (is_dev_type (dev, dev_type, "null")) + return DEV_TYPE_NULL; + else + return DEV_TYPE_UNDEF; +} + +const char * +dev_type_string (const char *dev, const char *dev_type) +{ + switch (dev_type_enum (dev, dev_type)) + { + case DEV_TYPE_TUN: + return "tun"; + case DEV_TYPE_TAP: + return "tap"; + case DEV_TYPE_NULL: + return "null"; + default: + return "[unknown-dev-type]"; + } +} + +const char * +dev_component_in_dev_node (const char *dev_node) +{ + const char *ret; + const int dirsep = OS_SPECIFIC_DIRSEP; + + if (dev_node) + { + ret = strrchr (dev_node, dirsep); + if (ret && *ret) + ++ret; + else + ret = dev_node; + if (*ret) + return ret; + } + return NULL; +} + +/* + * Try to predict the actual TUN/TAP device instance name, + * before the device is actually opened. + */ +const char * +guess_tuntap_dev (const char *dev, + const char *dev_type, + const char *dev_node, + struct gc_arena *gc) +{ +#ifdef WIN32 + const int dt = dev_type_enum (dev, dev_type); + if (dt == DEV_TYPE_TUN || dt == DEV_TYPE_TAP) + { + return get_netsh_id (dev_node, gc); + } +#endif + + /* default case */ + return dev; +} + +/* + * Called by the open_tun function of OSes to check if we + * explicitly support IPv6. + * + * In this context, explicit means that the OS expects us to + * do something special to the tun socket in order to support + * IPv6, i.e. it is not transparent. + * + * ipv6_explicitly_supported should be set to false if we don't + * have any explicit IPv6 code in the tun device handler. + * + * If ipv6_explicitly_supported is true, then we have explicit + * OS-specific tun dev code for handling IPv6. If so, tt->ipv6 + * is set according to the --tun-ipv6 command line option. + */ +static void +ipv6_support (bool ipv6, bool ipv6_explicitly_supported, struct tuntap* tt) +{ + tt->ipv6 = false; + if (ipv6_explicitly_supported) + tt->ipv6 = ipv6; + else if (ipv6) + msg (M_WARN, "NOTE: explicit support for IPv6 tun devices is not provided for this OS"); +} + +/* --ifconfig-nowarn disables some options sanity checking */ +static const char ifconfig_warn_how_to_silence[] = "(silence this warning with --ifconfig-nowarn)"; + +/* + * If !tun, make sure ifconfig_remote_netmask looks + * like a netmask. + * + * If tun, make sure ifconfig_remote_netmask looks + * like an IPv4 address. + */ +static void +ifconfig_sanity_check (bool tun, in_addr_t addr) +{ + struct gc_arena gc = gc_new (); + const bool looks_like_netmask = ((addr & 0xFF000000) == 0xFF000000); + if (tun) + { + if (looks_like_netmask) + msg (M_WARN, "WARNING: Since you are using --dev tun, the second argument to --ifconfig must be an IP address. You are using something (%s) that looks more like a netmask. %s", + print_in_addr_t (addr, 0, &gc), + ifconfig_warn_how_to_silence); + } + else /* tap */ + { + if (!looks_like_netmask) + msg (M_WARN, "WARNING: Since you are using --dev tap, the second argument to --ifconfig must be a netmask, for example something like 255.255.255.0. %s", + ifconfig_warn_how_to_silence); + } + gc_free (&gc); +} + +/* + * For TAP-style devices, generate a broadcast address. + */ +static in_addr_t +generate_ifconfig_broadcast_addr (in_addr_t local, + in_addr_t netmask) +{ + return local | ~netmask; +} + +/* + * Check that --local and --remote addresses do not + * clash with ifconfig addresses or subnet. + */ +static void +check_addr_clash (const char *name, + int type, + in_addr_t public, + in_addr_t local, + in_addr_t remote_netmask) +{ + struct gc_arena gc = gc_new (); +#if 0 + msg (M_INFO, "CHECK_ADDR_CLASH type=%d public=%s local=%s, remote_netmask=%s", + type, + print_in_addr_t (public, 0, &gc), + print_in_addr_t (local, 0, &gc), + print_in_addr_t (remote_netmask, 0, &gc)); +#endif + + if (public) + { + if (type == DEV_TYPE_TUN) + { + const in_addr_t test_netmask = 0xFFFFFF00; + const in_addr_t public_net = public & test_netmask; + const in_addr_t local_net = local & test_netmask; + const in_addr_t remote_net = remote_netmask & test_netmask; + + if (public == local || public == remote_netmask) + msg (M_WARN, + "WARNING: --%s address [%s] conflicts with --ifconfig address pair [%s, %s]. %s", + name, + print_in_addr_t (public, 0, &gc), + print_in_addr_t (local, 0, &gc), + print_in_addr_t (remote_netmask, 0, &gc), + ifconfig_warn_how_to_silence); + + if (public_net == local_net || public_net == remote_net) + msg (M_WARN, + "WARNING: potential conflict between --%s address [%s] and --ifconfig address pair [%s, %s] -- this is a warning only that is triggered when local/remote addresses exist within the same /24 subnet as --ifconfig endpoints. %s", + name, + print_in_addr_t (public, 0, &gc), + print_in_addr_t (local, 0, &gc), + print_in_addr_t (remote_netmask, 0, &gc), + ifconfig_warn_how_to_silence); + } + else if (type == DEV_TYPE_TAP) + { + const in_addr_t public_network = public & remote_netmask; + const in_addr_t virtual_network = local & remote_netmask; + if (public_network == virtual_network) + msg (M_WARN, + "WARNING: --%s address [%s] conflicts with --ifconfig subnet [%s, %s] -- local and remote addresses cannot be inside of the --ifconfig subnet. %s", + name, + print_in_addr_t (public, 0, &gc), + print_in_addr_t (local, 0, &gc), + print_in_addr_t (remote_netmask, 0, &gc), + ifconfig_warn_how_to_silence); + } + } + gc_free (&gc); +} + +/* + * Complain if --dev tap and --ifconfig is used on an OS for which + * we don't have a custom tap ifconfig template below. + */ +static void +no_tap_ifconfig () +{ + msg (M_FATAL, "Sorry but you cannot use --dev tap and --ifconfig together on this OS because I have not yet been programmed to understand the appropriate ifconfig syntax to use for TAP-style devices on this OS. Your best alternative is to use an --up script and do the ifconfig command manually."); +} + +/* + * Return a string to be used for options compatibility check + * between peers. + */ +const char * +ifconfig_options_string (const struct tuntap* tt, bool remote, bool disable, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (256, gc); + if (tt->did_ifconfig_setup && !disable) + { + if (tt->type == DEV_TYPE_TUN) + { + const char *l, *r; + if (remote) + { + r = print_in_addr_t (tt->local, 0, gc); + l = print_in_addr_t (tt->remote_netmask, 0, gc); + } + else + { + l = print_in_addr_t (tt->local, 0, gc); + r = print_in_addr_t (tt->remote_netmask, 0, gc); + } + buf_printf (&out, "%s %s", r, l); + } + else if (tt->type == DEV_TYPE_TAP) + { + buf_printf (&out, "%s %s", + print_in_addr_t (tt->local & tt->remote_netmask, 0, gc), + print_in_addr_t (tt->remote_netmask, 0, gc)); + } + else + buf_printf (&out, "[undef]"); + } + return BSTR (&out); +} + +/* + * Return a status string describing wait state. + */ +const char * +tun_stat (const struct tuntap *tt, unsigned int rwflags, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (64, gc); + if (tt) + { + if (rwflags & EVENT_READ) + { + buf_printf (&out, "T%s", + (tt->rwflags_debug & EVENT_READ) ? "R" : "r"); +#ifdef WIN32 + buf_printf (&out, "%s", + overlapped_io_state_ascii (&tt->reads)); +#endif + } + if (rwflags & EVENT_WRITE) + { + buf_printf (&out, "T%s", + (tt->rwflags_debug & EVENT_WRITE) ? "W" : "w"); +#ifdef WIN32 + buf_printf (&out, "%s", + overlapped_io_state_ascii (&tt->writes)); +#endif + } + } + else + { + buf_printf (&out, "T?"); + } + return BSTR (&out); +} + +/* + * Init tun/tap object. + * + * Set up tuntap structure for ifconfig, + * but don't execute yet. + */ +struct tuntap * +init_tun (const char *dev, /* --dev option */ + const char *dev_type, /* --dev-type option */ + const char *ifconfig_local_parm, /* --ifconfig parm 1 */ + const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */ + in_addr_t local_public, + in_addr_t remote_public, + const bool strict_warn, + struct env_set *es) +{ + struct gc_arena gc = gc_new (); + struct tuntap *tt; + + ALLOC_OBJ (tt, struct tuntap); + clear_tuntap (tt); + + tt->type = dev_type_enum (dev, dev_type); + + if (ifconfig_local_parm && ifconfig_remote_netmask_parm) + { + bool tun = false; + const char *ifconfig_local = NULL; + const char *ifconfig_remote_netmask = NULL; + const char *ifconfig_broadcast = NULL; + + /* + * We only handle TUN/TAP devices here, not --dev null devices. + */ + if (tt->type == DEV_TYPE_TUN) + tun = true; + else if (tt->type == DEV_TYPE_TAP) + tun = false; + else + msg (M_FATAL, "'%s' is not a TUN/TAP device. The --ifconfig option works only for TUN/TAP devices.", dev); + + /* + * Convert arguments to binary IPv4 addresses. + */ + + tt->local = getaddr ( + GETADDR_RESOLVE + | GETADDR_HOST_ORDER + | GETADDR_FATAL_ON_SIGNAL + | GETADDR_FATAL, + ifconfig_local_parm, + 0, + NULL, + NULL); + + tt->remote_netmask = getaddr ( + (tun ? GETADDR_RESOLVE : 0) + | GETADDR_HOST_ORDER + | GETADDR_FATAL_ON_SIGNAL + | GETADDR_FATAL, + ifconfig_remote_netmask_parm, + 0, + NULL, + NULL); + + /* + * Look for common errors in --ifconfig parms + */ + if (strict_warn) + { + ifconfig_sanity_check (tun, tt->remote_netmask); + + /* + * If local_public or remote_public addresses are defined, + * make sure they do not clash with our virtual subnet. + */ + + check_addr_clash ("local", + tt->type, + local_public, + tt->local, + tt->remote_netmask); + + check_addr_clash ("remote", + tt->type, + remote_public, + tt->local, + tt->remote_netmask); + } + + /* + * Set ifconfig parameters + */ + ifconfig_local = print_in_addr_t (tt->local, 0, &gc); + ifconfig_remote_netmask = print_in_addr_t (tt->remote_netmask, 0, &gc); + + /* + * If TAP-style interface, generate broadcast address. + */ + if (!tun) + { + tt->broadcast = generate_ifconfig_broadcast_addr (tt->local, tt->remote_netmask); + ifconfig_broadcast = print_in_addr_t (tt->broadcast, 0, &gc); + } + + /* + * Set environmental variables with ifconfig parameters. + */ + if (es) + { + setenv_str (es, "ifconfig_local", ifconfig_local); + if (tun) + { + setenv_str (es, "ifconfig_remote", ifconfig_remote_netmask); + } + else + { + setenv_str (es, "ifconfig_netmask", ifconfig_remote_netmask); + setenv_str (es, "ifconfig_broadcast", ifconfig_broadcast); + } + } + + tt->did_ifconfig_setup = true; + } + gc_free (&gc); + return tt; +} + +/* + * Platform specific tun initializations + */ +void +init_tun_post (struct tuntap *tt, + const struct frame *frame, + const struct tuntap_options *options) +{ + tt->options = *options; +#ifdef WIN32 + overlapped_io_init (&tt->reads, frame, FALSE, true); + overlapped_io_init (&tt->writes, frame, TRUE, true); + tt->rw_handle.read = tt->reads.overlapped.hEvent; + tt->rw_handle.write = tt->writes.overlapped.hEvent; + tt->adapter_index = ~0; +#endif +} + +/* execute the ifconfig command through the shell */ +void +do_ifconfig (struct tuntap *tt, + const char *actual, /* actual device name */ + int tun_mtu, + const struct env_set *es) +{ + struct gc_arena gc = gc_new (); + + if (tt->did_ifconfig_setup) + { + bool tun = false; + const char *ifconfig_local = NULL; + const char *ifconfig_remote_netmask = NULL; + const char *ifconfig_broadcast = NULL; + char command_line[256]; + + /* + * We only handle TUN/TAP devices here, not --dev null devices. + */ + if (tt->type == DEV_TYPE_TUN) + tun = true; + else if (tt->type == DEV_TYPE_TAP) + tun = false; + else + ASSERT (0); /* should have been caught in init_tun */ + + /* + * Set ifconfig parameters + */ + ifconfig_local = print_in_addr_t (tt->local, 0, &gc); + ifconfig_remote_netmask = print_in_addr_t (tt->remote_netmask, 0, &gc); + + /* + * If TAP-style device, generate broadcast address. + */ + if (!tun) + ifconfig_broadcast = print_in_addr_t (tt->broadcast, 0, &gc); + +#ifdef ENABLE_MANAGEMENT + if (management) + { + management_set_state (management, + OPENVPN_STATE_ASSIGN_IP, + NULL, + tt->local); + } +#endif + + +#if defined(TARGET_LINUX) +#ifdef CONFIG_FEATURE_IPROUTE + /* + * Set the MTU for the device + */ + openvpn_snprintf (command_line, sizeof (command_line), + IPROUTE_PATH " link set dev %s up mtu %d", + actual, + tun_mtu + ); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "Linux ip link set failed"); + + if (tun) { + + /* + * Set the address for the device + */ + openvpn_snprintf (command_line, sizeof (command_line), + IPROUTE_PATH " addr add dev %s local %s peer %s", + actual, + ifconfig_local, + ifconfig_remote_netmask + ); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "Linux ip addr add failed"); + } else { + openvpn_snprintf (command_line, sizeof (command_line), + IPROUTE_PATH " addr add dev %s %s/%d broadcast %s", + actual, + ifconfig_local, + count_netmask_bits(ifconfig_remote_netmask), + ifconfig_broadcast + ); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "Linux ip addr add failed"); + } + tt->did_ifconfig = true; +#else + if (tun) + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s pointopoint %s mtu %d", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + else + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu, + ifconfig_broadcast + ); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "Linux ifconfig failed"); + tt->did_ifconfig = true; + +#endif /*CONFIG_FEATURE_IPROUTE*/ +#elif defined(TARGET_SOLARIS) + + /* Solaris 2.6 (and 7?) cannot set all parameters in one go... + * example: + * ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 up + * ifconfig tun2 netmask 255.255.255.255 + */ + if (tun) + { + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s %s mtu %d up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + + msg (M_INFO, "%s", command_line); + if (!system_check (command_line, es, 0, "Solaris ifconfig phase-1 failed")) + solaris_error_close (tt, es, actual); + + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s netmask 255.255.255.255", + actual + ); + } + else + no_tap_ifconfig (); + + msg (M_INFO, "%s", command_line); + if (!system_check (command_line, es, 0, "Solaris ifconfig phase-2 failed")) + solaris_error_close (tt, es, actual); + + tt->did_ifconfig = true; + +#elif defined(TARGET_OPENBSD) + + /* + * OpenBSD tun devices appear to be persistent by default. It seems in order + * to make this work correctly, we need to delete the previous instance + * (if it exists), and re-ifconfig. Let me know if you know a better way. + */ + + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s destroy", + actual); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, 0, NULL); + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s create", + actual); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, 0, NULL); + msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure"); + + /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ + if (tun) + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + else + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s netmask %s mtu %d broadcast %s link0", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu, + ifconfig_broadcast + ); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "OpenBSD ifconfig failed"); + tt->did_ifconfig = true; + +#elif defined(TARGET_NETBSD) + + if (tun) + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + else + no_tap_ifconfig (); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "NetBSD ifconfig failed"); + tt->did_ifconfig = true; + +#elif defined(TARGET_DARWIN) + + /* + * Darwin (i.e. Mac OS X) seems to exhibit similar behaviour to OpenBSD... + */ + + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s delete", + actual); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, 0, NULL); + msg (M_INFO, "NOTE: Tried to delete pre-existing tun/tap instance -- No Problem if failure"); + + + /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ + if (tun) + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + else + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s netmask %s mtu %d up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "Mac OS X ifconfig failed"); + tt->did_ifconfig = true; + +#elif defined(TARGET_FREEBSD) + + /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ + if (tun) + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s %s mtu %d netmask 255.255.255.255 up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + else + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s %s netmask %s mtu %d up", + actual, + ifconfig_local, + ifconfig_remote_netmask, + tun_mtu + ); + + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "FreeBSD ifconfig failed"); + tt->did_ifconfig = true; + +#elif defined (WIN32) + { + const char *netmask; + + /* + * Make sure that both ifconfig addresses are part of the + * same .252 subnet. + */ + if (tun) + { + verify_255_255_255_252 (tt->local, tt->remote_netmask); + tt->adapter_netmask = ~3; + netmask = print_in_addr_t (tt->adapter_netmask, 0, &gc); + } + else + { + netmask = ifconfig_remote_netmask; + tt->adapter_netmask = tt->remote_netmask; + } + + /* example: netsh interface ip set address my-tap static 10.3.0.1 255.255.255.0 */ + openvpn_snprintf (command_line, sizeof (command_line), + "netsh interface ip set address \"%s\" static %s %s", + actual, + ifconfig_local, + netmask); + + switch (tt->options.ip_win32_type) + { + case IPW32_SET_MANUAL: + msg (M_INFO, "******** NOTE: Please manually set the IP/netmask of '%s' to %s/%s (if it is not already set)", + actual, + ifconfig_local, + netmask); + break; + case IPW32_SET_NETSH: + if (!strcmp (actual, "NULL")) + msg (M_FATAL, "Error: When using --ip-win32 netsh, if you have more than one TAP-Win32 adapter, you must also specify --dev-node"); + netcmd_semaphore_lock (); + msg (M_INFO, "%s", command_line); + system_check (command_line, es, S_FATAL, "ERROR: netsh command failed"); + netcmd_semaphore_release (); + break; + } + tt->did_ifconfig = true; + } + +#else + msg (M_FATAL, "Sorry, but I don't know how to do 'ifconfig' commands on this operating system. You should ifconfig your TUN/TAP device manually or use an --up script."); +#endif + } + gc_free (&gc); +} + +void +clear_tuntap (struct tuntap *tuntap) +{ + CLEAR (*tuntap); +#ifdef WIN32 + tuntap->hand = NULL; +#else + tuntap->fd = -1; +#endif +#ifdef TARGET_SOLARIS + tuntap->ip_fd = -1; +#endif + tuntap->ipv6 = false; +} + +static void +open_null (struct tuntap *tt) +{ + tt->actual_name = string_alloc ("null", NULL); +} + +#ifndef WIN32 +static void +open_tun_generic (const char *dev, const char *dev_type, const char *dev_node, + bool ipv6, bool ipv6_explicitly_supported, bool dynamic, + struct tuntap *tt) +{ + char tunname[256]; + char dynamic_name[256]; + bool dynamic_opened = false; + + ipv6_support (ipv6, ipv6_explicitly_supported, tt); + + if (tt->type == DEV_TYPE_NULL) + { + open_null (tt); + } + else + { + /* + * --dev-node specified, so open an explicit device node + */ + if (dev_node) + { + openvpn_snprintf (tunname, sizeof (tunname), "%s", dev_node); + } + else + { + /* + * dynamic open is indicated by --dev specified without + * explicit unit number. Try opening /dev/[dev]n + * where n = [0, 255]. + */ + if (dynamic && !has_digit(dev)) + { + int i; + for (i = 0; i < 256; ++i) + { + openvpn_snprintf (tunname, sizeof (tunname), + "/dev/%s%d", dev, i); + openvpn_snprintf (dynamic_name, sizeof (dynamic_name), + "%s%d", dev, i); + if ((tt->fd = open (tunname, O_RDWR)) > 0) + { + dynamic_opened = true; + break; + } + msg (D_READ_WRITE | M_ERRNO, "Tried opening %s (failed)", tunname); + } + if (!dynamic_opened) + msg (M_FATAL, "Cannot allocate TUN/TAP dev dynamically"); + } + /* + * explicit unit number specified + */ + else + { + openvpn_snprintf (tunname, sizeof (tunname), "/dev/%s", dev); + } + } + + if (!dynamic_opened) + { + if ((tt->fd = open (tunname, O_RDWR)) < 0) + msg (M_ERR, "Cannot open TUN/TAP dev %s", tunname); + } + + set_nonblock (tt->fd); + set_cloexec (tt->fd); /* don't pass fd to scripts */ + msg (M_INFO, "TUN/TAP device %s opened", tunname); + + /* tt->actual_name is passed to up and down scripts and used as the ifconfig dev name */ + tt->actual_name = string_alloc (dynamic_opened ? dynamic_name : dev, NULL); + } +} + +static void +close_tun_generic (struct tuntap *tt) +{ + if (tt->fd >= 0) + close (tt->fd); + if (tt->actual_name) + free (tt->actual_name); + clear_tuntap (tt); +} + +#endif + +#if defined(TARGET_LINUX) + +#ifdef HAVE_LINUX_IF_TUN_H /* New driver support */ + +#ifndef HAVE_LINUX_SOCKIOS_H +#error header file linux/sockios.h required +#endif + +#if defined(HAVE_TUN_PI) && defined(HAVE_IPHDR) && defined(HAVE_IOVEC) && defined(ETH_P_IPV6) && defined(ETH_P_IP) && defined(HAVE_READV) && defined(HAVE_WRITEV) +#define LINUX_IPV6 1 +/* #warning IPv6 ON */ +#else +#define LINUX_IPV6 0 +/* #warning IPv6 OFF */ +#endif + +#if !PEDANTIC + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + struct ifreq ifr; + + /* + * Set tt->ipv6 to true if + * (a) we have the capability of supporting --tun-ipv6, and + * (b) --tun-ipv6 was specified. + */ + ipv6_support (ipv6, LINUX_IPV6, tt); + + /* + * We handle --dev null specially, we do not open /dev/null for this. + */ + if (tt->type == DEV_TYPE_NULL) + { + open_null (tt); + } + else + { + /* + * Process --dev-node + */ + const char *node = dev_node; + if (!node) + node = "/dev/net/tun"; + + /* + * Open the interface + */ + if ((tt->fd = open (node, O_RDWR)) < 0) + { + msg (M_WARN | M_ERRNO, "Note: Cannot open TUN/TAP dev %s", node); + goto linux_2_2_fallback; + } + + /* + * Process --tun-ipv6 + */ + CLEAR (ifr); + if (!tt->ipv6) + ifr.ifr_flags = IFF_NO_PI; + +#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN) + ifr.ifr_flags |= IFF_ONE_QUEUE; +#endif + + /* + * Figure out if tun or tap device + */ + if (tt->type == DEV_TYPE_TUN) + { + ifr.ifr_flags |= IFF_TUN; + } + else if (tt->type == DEV_TYPE_TAP) + { + ifr.ifr_flags |= IFF_TAP; + } + else + { + msg (M_FATAL, "I don't recognize device %s as a tun or tap device", + dev); + } + + /* + * Set an explicit name, if --dev is not tun or tap + */ + if (strcmp(dev, "tun") && strcmp(dev, "tap")) + strncpynt (ifr.ifr_name, dev, IFNAMSIZ); + + /* + * Use special ioctl that configures tun/tap device with the parms + * we set in ifr + */ + if (ioctl (tt->fd, TUNSETIFF, (void *) &ifr) < 0) + { + msg (M_WARN | M_ERRNO, "Note: Cannot ioctl TUNSETIFF %s", dev); + goto linux_2_2_fallback; + } + + msg (M_INFO, "TUN/TAP device %s opened", ifr.ifr_name); + + /* + * Try making the TX send queue bigger + */ +#if defined(IFF_ONE_QUEUE) && defined(SIOCSIFTXQLEN) + { + struct ifreq netifr; + int ctl_fd; + + if ((ctl_fd = socket (AF_INET, SOCK_DGRAM, 0)) >= 0) + { + CLEAR (netifr); + strncpynt (netifr.ifr_name, ifr.ifr_name, IFNAMSIZ); + netifr.ifr_qlen = tt->options.txqueuelen; + if (ioctl (ctl_fd, SIOCSIFTXQLEN, (void *) &netifr) >= 0) + msg (D_OSBUF, "TUN/TAP TX queue length set to %d", tt->options.txqueuelen); + else + msg (M_WARN | M_ERRNO, "Note: Cannot set tx queue length on %s", ifr.ifr_name); + close (ctl_fd); + } + else + { + msg (M_WARN | M_ERRNO, "Note: Cannot open control socket on %s", ifr.ifr_name); + } + } +#endif + + set_nonblock (tt->fd); + set_cloexec (tt->fd); + tt->actual_name = string_alloc (ifr.ifr_name, NULL); + } + return; + + linux_2_2_fallback: + msg (M_INFO, "Note: Attempting fallback to kernel 2.2 TUN/TAP interface"); + if (tt->fd >= 0) + { + close (tt->fd); + tt->fd = -1; + } + open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); +} + +#else + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + ASSERT (0); +} + +#endif + +#else + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); +} + +#endif /* HAVE_LINUX_IF_TUN_H */ + +#ifdef TUNSETPERSIST + +void +tuncfg (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, int persist_mode) +{ + struct tuntap *tt; + + ALLOC_OBJ (tt, struct tuntap); + clear_tuntap (tt); + tt->type = dev_type_enum (dev, dev_type); + open_tun (dev, dev_type, dev_node, ipv6, tt); + if (ioctl (tt->fd, TUNSETPERSIST, persist_mode) < 0) + msg (M_ERR, "Cannot ioctl TUNSETPERSIST(%d) %s", persist_mode, dev); + close_tun (tt); + msg (M_INFO, "Persist state set to: %s", (persist_mode ? "ON" : "OFF")); +} + +#endif /* TUNSETPERSIST */ + +void +close_tun (struct tuntap *tt) +{ + if (tt) + { + close_tun_generic (tt); + free (tt); + } +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ +#if LINUX_IPV6 + if (tt->ipv6) + { + struct tun_pi pi; + struct iphdr *iph; + struct iovec vect[2]; + int ret; + + iph = (struct iphdr *)buf; + + pi.flags = 0; + + if(iph->version == 6) + pi.proto = htons(ETH_P_IPV6); + else + pi.proto = htons(ETH_P_IP); + + vect[0].iov_len = sizeof(pi); + vect[0].iov_base = π + vect[1].iov_len = len; + vect[1].iov_base = buf; + + ret = writev(tt->fd, vect, 2); + return(ret - sizeof(pi)); + } + else +#endif + return write (tt->fd, buf, len); +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ +#if LINUX_IPV6 + if (tt->ipv6) + { + struct iovec vect[2]; + struct tun_pi pi; + int ret; + + vect[0].iov_len = sizeof(pi); + vect[0].iov_base = π + vect[1].iov_len = len; + vect[1].iov_base = buf; + + ret = readv(tt->fd, vect, 2); + return(ret - sizeof(pi)); + } + else +#endif + return read (tt->fd, buf, len); +} + +#elif defined(TARGET_SOLARIS) + +#ifndef TUNNEWPPA +#error I need the symbol TUNNEWPPA from net/if_tun.h +#endif + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + int if_fd, muxid, ppa = -1; + struct ifreq ifr; + const char *ptr; + const char *ip_node; + const char *dev_tuntap_type; + int link_type; + bool is_tun; + + ipv6_support (ipv6, false, tt); + + if (tt->type == DEV_TYPE_NULL) + { + open_null (tt); + return; + } + + if (tt->type == DEV_TYPE_TUN) + { + ip_node = "/dev/udp"; + if (!dev_node) + dev_node = "/dev/tun"; + dev_tuntap_type = "tun"; + link_type = I_PLINK; + is_tun = true; + } + else if (tt->type == DEV_TYPE_TAP) + { + ip_node = "/dev/ip"; + if (!dev_node) + dev_node = "/dev/tap"; + dev_tuntap_type = "tap"; + link_type = I_PLINK; /* was: I_LINK */ + is_tun = false; + } + else + { + msg (M_FATAL, "I don't recognize device %s as a tun or tap device", + dev); + } + + /* get unit number */ + if (*dev) + { + ptr = dev; + while (*ptr && !isdigit ((int) *ptr)) + ptr++; + ppa = atoi (ptr); + } + + if ((tt->ip_fd = open (ip_node, O_RDWR, 0)) < 0) + msg (M_ERR, "Can't open %s", ip_node); + + if ((tt->fd = open (dev_node, O_RDWR, 0)) < 0) + msg (M_ERR, "Can't open %s", dev_node); + + /* Assign a new PPA and get its unit number. */ + if ((ppa = ioctl (tt->fd, TUNNEWPPA, ppa)) < 0) + msg (M_ERR, "Can't assign new interface"); + + if ((if_fd = open (dev_node, O_RDWR, 0)) < 0) + msg (M_ERR, "Can't open %s (2)", dev_node); + + if (ioctl (if_fd, I_PUSH, "ip") < 0) + msg (M_ERR, "Can't push IP module"); + + /* Assign ppa according to the unit number returned by tun device */ + if (ioctl (if_fd, IF_UNITSEL, (char *) &ppa) < 0) + msg (M_ERR, "Can't set PPA %d", ppa); + + if ((muxid = ioctl (tt->ip_fd, link_type, if_fd)) < 0) + msg (M_ERR, "Can't link %s device to IP", dev_tuntap_type); + + close (if_fd); + + tt->actual_name = (char *) malloc (32); + check_malloc_return (tt->actual_name); + + openvpn_snprintf (tt->actual_name, 32, "%s%d", dev_tuntap_type, ppa); + + CLEAR (ifr); + strncpynt (ifr.ifr_name, tt->actual_name, sizeof (ifr.ifr_name)); + ifr.ifr_ip_muxid = muxid; + + if (ioctl (tt->ip_fd, SIOCSIFMUXID, &ifr) < 0) + { + ioctl (tt->ip_fd, I_PUNLINK, muxid); + msg (M_ERR, "Can't set multiplexor id"); + } + + set_nonblock (tt->fd); + set_cloexec (tt->fd); + set_cloexec (tt->ip_fd); + + msg (M_INFO, "TUN/TAP device %s opened", tt->actual_name); +} + +static void +solaris_close_tun (struct tuntap *tt) +{ + if (tt) + { + if (tt->ip_fd >= 0) + { + struct ifreq ifr; + CLEAR (ifr); + strncpynt (ifr.ifr_name, tt->actual_name, sizeof (ifr.ifr_name)); + + if (ioctl (tt->ip_fd, SIOCGIFFLAGS, &ifr) < 0) + msg (M_WARN | M_ERRNO, "Can't get iface flags"); + + if (ioctl (tt->ip_fd, SIOCGIFMUXID, &ifr) < 0) + msg (M_WARN | M_ERRNO, "Can't get multiplexor id"); + + if (ioctl (tt->ip_fd, I_PUNLINK, ifr.ifr_ip_muxid) < 0) + msg (M_WARN | M_ERRNO, "Can't unlink interface"); + + close (tt->ip_fd); + tt->ip_fd = -1; + } + + if (tt->fd >= 0) + { + close (tt->fd); + tt->fd = -1; + } + } +} + +/* + * Close TUN device. + */ +void +close_tun (struct tuntap *tt) +{ + if (tt) + { + solaris_close_tun (tt); + + if (tt->actual_name) + free (tt->actual_name); + + clear_tuntap (tt); + free (tt); + } +} + +static void +solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual) +{ + char command_line[256]; + + openvpn_snprintf (command_line, sizeof (command_line), + IFCONFIG_PATH " %s unplumb", + actual); + + msg (M_INFO, "%s", command_line); + system_check (command_line, es, 0, "Solaris ifconfig unplumb failed"); + close_tun (tt); + msg (M_FATAL, "Solaris ifconfig failed"); +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + struct strbuf sbuf; + sbuf.len = len; + sbuf.buf = (char *)buf; + return putmsg (tt->fd, NULL, &sbuf, 0) >= 0 ? sbuf.len : -1; +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + struct strbuf sbuf; + int f = 0; + + sbuf.maxlen = len; + sbuf.buf = (char *)buf; + return getmsg (tt->fd, NULL, &sbuf, &f) >= 0 ? sbuf.len : -1; +} + +#elif defined(TARGET_OPENBSD) + +#if !defined(HAVE_READV) || !defined(HAVE_WRITEV) +#error openbsd build requires readv & writev library functions +#endif + +/* + * OpenBSD has a slightly incompatible TUN device from + * the rest of the world, in that it prepends a + * uint32 to the beginning of the IP header + * to designate the protocol (why not just + * look at the version field in the IP header to + * determine v4 or v6?). + * + * We strip off this field on reads and + * put it back on writes. + * + * I have not tested TAP devices on OpenBSD, + * but I have conditionalized the special + * TUN handling code described above to + * go away for TAP devices. + */ + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + + /* Enable multicast on the interface */ + if (tt->fd >= 0) + { + struct tuninfo info; + + if (ioctl (tt->fd, TUNGIFINFO, &info) < 0) { + msg (M_WARN | M_ERRNO, "Can't get interface info: %s", + strerror(errno)); + } + + info.flags |= IFF_MULTICAST; + + if (ioctl (tt->fd, TUNSIFINFO, &info) < 0) { + msg (M_WARN | M_ERRNO, "Can't set interface info: %s", + strerror(errno)); + } + } +} + +void +close_tun (struct tuntap* tt) +{ + if (tt) + { + close_tun_generic (tt); + free (tt); + } +} + +static inline int +openbsd_modify_read_write_return (int len) +{ + if (len > 0) + return len > sizeof (u_int32_t) ? len - sizeof (u_int32_t) : 0; + else + return len; +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + if (tt->type == DEV_TYPE_TUN) + { + u_int32_t type; + struct iovec iv[2]; + struct ip *iph; + + iph = (struct ip *) buf; + + if (tt->ipv6 && iph->ip_v == 6) + type = htonl (AF_INET6); + else + type = htonl (AF_INET); + + iv[0].iov_base = &type; + iv[0].iov_len = sizeof (type); + iv[1].iov_base = buf; + iv[1].iov_len = len; + + return openbsd_modify_read_write_return (writev (tt->fd, iv, 2)); + } + else + return write (tt->fd, buf, len); +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + if (tt->type == DEV_TYPE_TUN) + { + u_int32_t type; + struct iovec iv[2]; + + iv[0].iov_base = &type; + iv[0].iov_len = sizeof (type); + iv[1].iov_base = buf; + iv[1].iov_len = len; + + return openbsd_modify_read_write_return (readv (tt->fd, iv, 2)); + } + else + return read (tt->fd, buf, len); +} + +#elif defined(TARGET_NETBSD) + +/* + * NetBSD does not support IPv6 on tun out of the box, + * but there exists a patch. When this patch is applied, + * only two things are left to openvpn: + * 1. Activate multicasting (this has already been done + * before by the kernel, but we make sure that nobody + * has deactivated multicasting inbetween. + * 2. Deactivate "link layer mode" (otherwise NetBSD + * prepends the address family to the packet, and we + * would run into the same trouble as with OpenBSD. + */ + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + if (tt->fd >= 0) + { + int i = IFF_POINTOPOINT|IFF_MULTICAST; + ioctl (tt->fd, TUNSIFMODE, &i); /* multicast on */ + i = 0; + ioctl (tt->fd, TUNSLMODE, &i); /* link layer mode off */ + } +} + +void +close_tun (struct tuntap *tt) +{ + if (tt) + { + close_tun_generic (tt); + free (tt); + } +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + return write (tt->fd, buf, len); +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + return read (tt->fd, buf, len); +} + +#elif defined(TARGET_FREEBSD) + +static inline int +freebsd_modify_read_write_return (int len) +{ + if (len > 0) + return len > sizeof (u_int32_t) ? len - sizeof (u_int32_t) : 0; + else + return len; +} + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + + if (tt->fd >= 0) + { + int i = 0; + + /* Disable extended modes */ + ioctl (tt->fd, TUNSLMODE, &i); + i = 1; + ioctl (tt->fd, TUNSIFHEAD, &i); + } +} + +void +close_tun (struct tuntap *tt) +{ + if (tt) + { + close_tun_generic (tt); + free (tt); + } +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + if (tt->type == DEV_TYPE_TUN) + { + u_int32_t type; + struct iovec iv[2]; + struct ip *iph; + + iph = (struct ip *) buf; + + if (tt->ipv6 && iph->ip_v == 6) + type = htonl (AF_INET6); + else + type = htonl (AF_INET); + + iv[0].iov_base = (char *)&type; + iv[0].iov_len = sizeof (type); + iv[1].iov_base = buf; + iv[1].iov_len = len; + + return freebsd_modify_read_write_return (writev (tt->fd, iv, 2)); + } + else + return write (tt->fd, buf, len); +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + if (tt->type == DEV_TYPE_TUN) + { + u_int32_t type; + struct iovec iv[2]; + + iv[0].iov_base = (char *)&type; + iv[0].iov_len = sizeof (type); + iv[1].iov_base = buf; + iv[1].iov_len = len; + + return freebsd_modify_read_write_return (readv (tt->fd, iv, 2)); + } + else + return read (tt->fd, buf, len); +} + +#elif defined(WIN32) + +int +tun_read_queue (struct tuntap *tt, int maxsize) +{ + if (tt->reads.iostate == IOSTATE_INITIAL) + { + DWORD len; + BOOL status; + int err; + + /* reset buf to its initial state */ + tt->reads.buf = tt->reads.buf_init; + + len = maxsize ? maxsize : BLEN (&tt->reads.buf); + ASSERT (len <= BLEN (&tt->reads.buf)); + + /* the overlapped read will signal this event on I/O completion */ + ASSERT (ResetEvent (tt->reads.overlapped.hEvent)); + + status = ReadFile( + tt->hand, + BPTR (&tt->reads.buf), + len, + &tt->reads.size, + &tt->reads.overlapped + ); + + if (status) /* operation completed immediately? */ + { + /* since we got an immediate return, we must signal the event object ourselves */ + ASSERT (SetEvent (tt->reads.overlapped.hEvent)); + + tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->reads.status = 0; + + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read immediate return [%d,%d]", + (int) len, + (int) tt->reads.size); + } + else + { + err = GetLastError (); + if (err == ERROR_IO_PENDING) /* operation queued? */ + { + tt->reads.iostate = IOSTATE_QUEUED; + tt->reads.status = err; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read queued [%d]", + (int) len); + } + else /* error occurred */ + { + struct gc_arena gc = gc_new (); + ASSERT (SetEvent (tt->reads.overlapped.hEvent)); + tt->reads.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->reads.status = err; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Read error [%d] : %s", + (int) len, + strerror_win32 (status, &gc)); + gc_free (&gc); + } + } + } + return tt->reads.iostate; +} + +int +tun_write_queue (struct tuntap *tt, struct buffer *buf) +{ + if (tt->writes.iostate == IOSTATE_INITIAL) + { + BOOL status; + int err; + + /* make a private copy of buf */ + tt->writes.buf = tt->writes.buf_init; + tt->writes.buf.len = 0; + ASSERT (buf_copy (&tt->writes.buf, buf)); + + /* the overlapped write will signal this event on I/O completion */ + ASSERT (ResetEvent (tt->writes.overlapped.hEvent)); + + status = WriteFile( + tt->hand, + BPTR (&tt->writes.buf), + BLEN (&tt->writes.buf), + &tt->writes.size, + &tt->writes.overlapped + ); + + if (status) /* operation completed immediately? */ + { + tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN; + + /* since we got an immediate return, we must signal the event object ourselves */ + ASSERT (SetEvent (tt->writes.overlapped.hEvent)); + + tt->writes.status = 0; + + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write immediate return [%d,%d]", + BLEN (&tt->writes.buf), + (int) tt->writes.size); + } + else + { + err = GetLastError (); + if (err == ERROR_IO_PENDING) /* operation queued? */ + { + tt->writes.iostate = IOSTATE_QUEUED; + tt->writes.status = err; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write queued [%d]", + BLEN (&tt->writes.buf)); + } + else /* error occurred */ + { + struct gc_arena gc = gc_new (); + ASSERT (SetEvent (tt->writes.overlapped.hEvent)); + tt->writes.iostate = IOSTATE_IMMEDIATE_RETURN; + tt->writes.status = err; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Write error [%d] : %s", + BLEN (&tt->writes.buf), + strerror_win32 (err, &gc)); + gc_free (&gc); + } + } + } + return tt->writes.iostate; +} + +int +tun_finalize ( + HANDLE h, + struct overlapped_io *io, + struct buffer *buf) +{ + int ret = -1; + BOOL status; + + switch (io->iostate) + { + case IOSTATE_QUEUED: + status = GetOverlappedResult( + h, + &io->overlapped, + &io->size, + FALSE + ); + if (status) + { + /* successful return for a queued operation */ + if (buf) + *buf = io->buf; + ret = io->size; + io->iostate = IOSTATE_INITIAL; + ASSERT (ResetEvent (io->overlapped.hEvent)); + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion success [%d]", ret); + } + else + { + /* error during a queued operation */ + ret = -1; + if (GetLastError() != ERROR_IO_INCOMPLETE) + { + /* if no error (i.e. just not finished yet), + then DON'T execute this code */ + io->iostate = IOSTATE_INITIAL; + ASSERT (ResetEvent (io->overlapped.hEvent)); + msg (D_WIN32_IO | M_ERRNO, "WIN32 I/O: TAP Completion error"); + } + } + break; + + case IOSTATE_IMMEDIATE_RETURN: + io->iostate = IOSTATE_INITIAL; + ASSERT (ResetEvent (io->overlapped.hEvent)); + if (io->status) + { + /* error return for a non-queued operation */ + SetLastError (io->status); + ret = -1; + msg (D_WIN32_IO | M_ERRNO, "WIN32 I/O: TAP Completion non-queued error"); + } + else + { + /* successful return for a non-queued operation */ + if (buf) + *buf = io->buf; + ret = io->size; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion non-queued success [%d]", ret); + } + break; + + case IOSTATE_INITIAL: /* were we called without proper queueing? */ + SetLastError (ERROR_INVALID_FUNCTION); + ret = -1; + dmsg (D_WIN32_IO, "WIN32 I/O: TAP Completion BAD STATE"); + break; + + default: + ASSERT (0); + } + + if (buf) + buf->len = ret; + return ret; +} + +const struct tap_reg * +get_tap_reg (struct gc_arena *gc) +{ + HKEY adapter_key; + LONG status; + DWORD len; + struct tap_reg *first = NULL; + struct tap_reg *last = NULL; + int i = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + ADAPTER_KEY, + 0, + KEY_READ, + &adapter_key); + + if (status != ERROR_SUCCESS) + msg (M_FATAL, "Error opening registry key: %s", ADAPTER_KEY); + + while (true) + { + char enum_name[256]; + char unit_string[256]; + HKEY unit_key; + char component_id_string[] = "ComponentId"; + char component_id[256]; + char net_cfg_instance_id_string[] = "NetCfgInstanceId"; + char net_cfg_instance_id[256]; + DWORD data_type; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + adapter_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + msg (M_FATAL, "Error enumerating registry subkeys of key: %s", + ADAPTER_KEY); + + openvpn_snprintf (unit_string, sizeof(unit_string), "%s\\%s", + ADAPTER_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + unit_string, + 0, + KEY_READ, + &unit_key); + + if (status != ERROR_SUCCESS) + dmsg (D_REGISTRY, "Error opening registry key: %s", unit_string); + else + { + len = sizeof (component_id); + status = RegQueryValueEx( + unit_key, + component_id_string, + NULL, + &data_type, + component_id, + &len); + + if (status != ERROR_SUCCESS || data_type != REG_SZ) + dmsg (D_REGISTRY, "Error opening registry key: %s\\%s", + unit_string, component_id_string); + else + { + len = sizeof (net_cfg_instance_id); + status = RegQueryValueEx( + unit_key, + net_cfg_instance_id_string, + NULL, + &data_type, + net_cfg_instance_id, + &len); + + if (status == ERROR_SUCCESS && data_type == REG_SZ) + { + if (!strcmp (component_id, TAP_COMPONENT_ID)) + { + struct tap_reg *reg; + ALLOC_OBJ_CLEAR_GC (reg, struct tap_reg, gc); + reg->guid = string_alloc (net_cfg_instance_id, gc); + + /* link into return list */ + if (!first) + first = reg; + if (last) + last->next = reg; + last = reg; + } + } + } + RegCloseKey (unit_key); + } + ++i; + } + + RegCloseKey (adapter_key); + return first; +} + +const struct panel_reg * +get_panel_reg (struct gc_arena *gc) +{ + LONG status; + HKEY network_connections_key; + DWORD len; + struct panel_reg *first = NULL; + struct panel_reg *last = NULL; + int i = 0; + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + NETWORK_CONNECTIONS_KEY, + 0, + KEY_READ, + &network_connections_key); + + if (status != ERROR_SUCCESS) + msg (M_FATAL, "Error opening registry key: %s", NETWORK_CONNECTIONS_KEY); + + while (true) + { + char enum_name[256]; + char connection_string[256]; + HKEY connection_key; + char name_data[256]; + DWORD name_type; + const char name_string[] = "Name"; + + len = sizeof (enum_name); + status = RegEnumKeyEx( + network_connections_key, + i, + enum_name, + &len, + NULL, + NULL, + NULL, + NULL); + if (status == ERROR_NO_MORE_ITEMS) + break; + else if (status != ERROR_SUCCESS) + msg (M_FATAL, "Error enumerating registry subkeys of key: %s", + NETWORK_CONNECTIONS_KEY); + + openvpn_snprintf (connection_string, sizeof(connection_string), + "%s\\%s\\Connection", + NETWORK_CONNECTIONS_KEY, enum_name); + + status = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + connection_string, + 0, + KEY_READ, + &connection_key); + + if (status != ERROR_SUCCESS) + dmsg (D_REGISTRY, "Error opening registry key: %s", connection_string); + else + { + len = sizeof (name_data); + status = RegQueryValueEx( + connection_key, + name_string, + NULL, + &name_type, + name_data, + &len); + + if (status != ERROR_SUCCESS || name_type != REG_SZ) + dmsg (D_REGISTRY, "Error opening registry key: %s\\%s\\%s", + NETWORK_CONNECTIONS_KEY, connection_string, name_string); + else + { + struct panel_reg *reg; + + ALLOC_OBJ_CLEAR_GC (reg, struct panel_reg, gc); + reg->name = string_alloc (name_data, gc); + reg->guid = string_alloc (enum_name, gc); + + /* link into return list */ + if (!first) + first = reg; + if (last) + last->next = reg; + last = reg; + } + RegCloseKey (connection_key); + } + ++i; + } + + RegCloseKey (network_connections_key); + + return first; +} + +/* + * Check that two addresses are part of the same 255.255.255.252 subnet. + */ +void +verify_255_255_255_252 (in_addr_t local, in_addr_t remote) +{ + struct gc_arena gc = gc_new (); + const unsigned int mask = 3; + const char *err = NULL; + + if (local == remote) + { + err = "must be different"; + goto error; + } + if ((local & (~mask)) != (remote & (~mask))) + { + err = "must exist within the same 255.255.255.252 subnet. This is a limitation of --dev tun when used with the TAP-WIN32 driver"; + goto error; + } + if ((local & mask) == 0 + || (local & mask) == 3 + || (remote & mask) == 0 + || (remote & mask) == 3) + { + err = "cannot use the first or last address within a given 255.255.255.252 subnet. This is a limitation of --dev tun when used with the TAP-WIN32 driver"; + goto error; + } + + gc_free (&gc); + return; + + error: + msg (M_FATAL, "There is a problem in your selection of --ifconfig endpoints [local=%s, remote=%s]. The local and remote VPN endpoints %s. Try '" PACKAGE " --show-valid-subnets' option for more info.", + print_in_addr_t (local, 0, &gc), + print_in_addr_t (remote, 0, &gc), + err); + gc_free (&gc); +} + +void show_valid_win32_tun_subnets (void) +{ + int i; + int col = 0; + + printf ("On Windows, point-to-point IP support (i.e. --dev tun)\n"); + printf ("is emulated by the TAP-Win32 driver. The major limitation\n"); + printf ("imposed by this approach is that the --ifconfig local and\n"); + printf ("remote endpoints must be part of the same 255.255.255.252\n"); + printf ("subnet. The following list shows examples of endpoint\n"); + printf ("pairs which satisfy this requirement. Only the final\n"); + printf ("component of the IP address pairs is at issue.\n\n"); + printf ("As an example, the following option would be correct:\n"); + printf (" --ifconfig 10.7.0.5 10.7.0.6 (on host A)\n"); + printf (" --ifconfig 10.7.0.6 10.7.0.5 (on host B)\n"); + printf ("because [5,6] is part of the below list.\n\n"); + + for (i = 0; i < 256; i += 4) + { + printf("[%3d,%3d] ", i+1, i+2); + if (++col > 4) + { + col = 0; + printf ("\n"); + } + } + if (col) + printf ("\n"); +} + +void +show_tap_win32_adapters (int msglev, int warnlev) +{ + struct gc_arena gc = gc_new (); + + bool warn_panel_null = false; + bool warn_panel_dup = false; + bool warn_tap_dup = false; + + int links; + + const struct tap_reg *tr; + const struct tap_reg *tr1; + const struct panel_reg *pr; + + const struct tap_reg *tap_reg = get_tap_reg (&gc); + const struct panel_reg *panel_reg = get_panel_reg (&gc); + + msg (msglev, "Available TAP-WIN32 adapters [name, GUID]:"); + + /* loop through each TAP-Win32 adapter registry entry */ + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + links = 0; + + /* loop through each network connections entry in the control panel */ + for (pr = panel_reg; pr != NULL; pr = pr->next) + { + if (!strcmp (tr->guid, pr->guid)) + { + msg (msglev, "'%s' %s", pr->name, tr->guid); + ++links; + } + } + + if (links > 1) + { + warn_panel_dup = true; + } + else if (links == 0) + { + /* a TAP adapter exists without a link from the network + connections control panel */ + warn_panel_null = true; + msg (msglev, "[NULL] %s", tr->guid); + } + } + + /* check for TAP-Win32 adapter duplicated GUIDs */ + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + for (tr1 = tap_reg; tr1 != NULL; tr1 = tr1->next) + { + if (tr != tr1 && !strcmp (tr->guid, tr1->guid)) + warn_tap_dup = true; + } + } + + /* warn on registry inconsistencies */ + if (warn_tap_dup) + msg (warnlev, "WARNING: Some TAP-Win32 adapters have duplicate GUIDs"); + + if (warn_panel_dup) + msg (warnlev, "WARNING: Some TAP-Win32 adapters have duplicate links from the Network Connections control panel"); + + if (warn_panel_null) + msg (warnlev, "WARNING: Some TAP-Win32 adapters have no link from the Network Connections control panel"); + + gc_free (&gc); +} + +/* + * Confirm that GUID is a TAP-Win32 adapter. + */ +static bool +is_tap_win32 (const char *guid, const struct tap_reg *tap_reg) +{ + const struct tap_reg *tr; + + for (tr = tap_reg; tr != NULL; tr = tr->next) + { + if (guid && !strcmp (tr->guid, guid)) + return true; + } + + return false; +} + +static const char * +guid_to_name (const char *guid, const struct panel_reg *panel_reg) +{ + const struct panel_reg *pr; + + for (pr = panel_reg; pr != NULL; pr = pr->next) + { + if (guid && !strcmp (pr->guid, guid)) + return pr->name; + } + + return NULL; +} + +static const char * +name_to_guid (const char *name, const struct tap_reg *tap_reg, const struct panel_reg *panel_reg) +{ + const struct panel_reg *pr; + + for (pr = panel_reg; pr != NULL; pr = pr->next) + { + if (name && !strcmp (pr->name, name) && is_tap_win32 (pr->guid, tap_reg)) + return pr->guid; + } + + return NULL; +} + +static void +at_least_one_tap_win32 (const struct tap_reg *tap_reg) +{ + if (!tap_reg) + msg (M_FATAL, "There are no TAP-Win32 adapters on this system. You should be able to create a TAP-Win32 adapter by going to Start -> All Programs -> " PACKAGE_NAME " -> Add a new TAP-Win32 virtual ethernet adapter."); +} + +/* + * Get an adapter GUID and optional actual_name from the + * registry for the TAP device # = device_number. + */ +static const char * +get_unspecified_device_guid (const int device_number, + char *actual_name, + int actual_name_size, + const struct tap_reg *tap_reg_src, + const struct panel_reg *panel_reg_src, + struct gc_arena *gc) +{ + const struct tap_reg *tap_reg = tap_reg_src; + struct buffer ret = clear_buf (); + struct buffer actual = clear_buf (); + int i; + + ASSERT (device_number >= 0); + + /* Make sure we have at least one TAP adapter */ + if (!tap_reg) + return NULL; + + /* The actual_name output buffer may be NULL */ + if (actual_name) + { + ASSERT (actual_name_size > 0); + buf_set_write (&actual, actual_name, actual_name_size); + } + + /* Move on to specified device number */ + for (i = 0; i < device_number; i++) + { + tap_reg = tap_reg->next; + if (!tap_reg) + return NULL; + } + + /* Save Network Panel name (if exists) in actual_name */ + if (actual_name) + { + const char *act = guid_to_name (tap_reg->guid, panel_reg_src); + if (act) + buf_printf (&actual, "%s", act); + else + buf_printf (&actual, "NULL"); + } + + /* Save GUID for return value */ + ret = alloc_buf_gc (256, gc); + buf_printf (&ret, "%s", tap_reg->guid); + return BSTR (&ret); +} + +/* + * Lookup a --dev-node adapter name in the registry + * returning the GUID and optional actual_name. + */ +static const char * +get_device_guid (const char *name, + char *actual_name, + int actual_name_size, + const struct tap_reg *tap_reg, + const struct panel_reg *panel_reg, + struct gc_arena *gc) +{ + struct buffer ret = alloc_buf_gc (256, gc); + struct buffer actual = clear_buf (); + + /* Make sure we have at least one TAP adapter */ + if (!tap_reg) + return NULL; + + /* The actual_name output buffer may be NULL */ + if (actual_name) + { + ASSERT (actual_name_size > 0); + buf_set_write (&actual, actual_name, actual_name_size); + } + + /* Check if GUID was explicitly specified as --dev-node parameter */ + if (is_tap_win32 (name, tap_reg)) + { + const char *act = guid_to_name (name, panel_reg); + buf_printf (&ret, "%s", name); + if (act) + buf_printf (&actual, "%s", act); + else + buf_printf (&actual, "NULL"); + return BSTR (&ret); + } + + /* Lookup TAP adapter in network connections list */ + { + const char *guid = name_to_guid (name, tap_reg, panel_reg); + if (guid) + { + buf_printf (&actual, "%s", name); + buf_printf (&ret, "%s", guid); + return BSTR (&ret); + } + } + + return NULL; +} + +/* + * Return a TAP name for netsh commands. + */ +const char * +get_netsh_id (const char *dev_node, struct gc_arena *gc) +{ + const struct tap_reg *tap_reg = get_tap_reg (gc); + const struct panel_reg *panel_reg = get_panel_reg (gc); + struct buffer actual = alloc_buf_gc (256, gc); + const char *guid; + + at_least_one_tap_win32 (tap_reg); + + if (dev_node) + { + guid = get_device_guid (dev_node, BPTR (&actual), BCAP (&actual), tap_reg, panel_reg, gc); + } + else + { + guid = get_unspecified_device_guid (0, BPTR (&actual), BCAP (&actual), tap_reg, panel_reg, gc); + + if (get_unspecified_device_guid (1, NULL, 0, tap_reg, panel_reg, gc)) /* ambiguous if more than one TAP-Win32 adapter */ + guid = NULL; + } + + if (!guid) + return "NULL"; /* not found */ + else if (strcmp (BPTR (&actual), "NULL")) + return BPTR (&actual); /* control panel name */ + else + return guid; /* no control panel name, return GUID instead */ +} + +/* + * Get adapter info list + */ +const IP_ADAPTER_INFO * +get_adapter_info_list (struct gc_arena *gc) +{ + ULONG size = 0; + IP_ADAPTER_INFO *pi = NULL; + DWORD status; + + if ((status = GetAdaptersInfo (NULL, &size)) != ERROR_BUFFER_OVERFLOW) + { + msg (M_INFO, "GetAdaptersInfo #1 failed (status=%u) : %s", + (unsigned int)status, + strerror_win32 (status, gc)); + } + else + { + pi = (PIP_ADAPTER_INFO) gc_malloc (size, false, gc); + if ((status = GetAdaptersInfo (pi, &size)) == NO_ERROR) + return pi; + else + { + msg (M_INFO, "GetAdaptersInfo #2 failed (status=%u) : %s", + (unsigned int)status, + strerror_win32 (status, gc)); + } + } + return pi; +} + +static const IP_INTERFACE_INFO * +get_interface_info_list (struct gc_arena *gc) +{ + ULONG size = 0; + IP_INTERFACE_INFO *ii = NULL; + DWORD status; + + if ((status = GetInterfaceInfo (NULL, &size)) != ERROR_INSUFFICIENT_BUFFER) + { + msg (M_INFO, "GetInterfaceInfo #1 failed (status=%u) : %s", + (unsigned int)status, + strerror_win32 (status, gc)); + } + else + { + ii = (PIP_INTERFACE_INFO) gc_malloc (size, false, gc); + if ((status = GetInterfaceInfo (ii, &size)) == NO_ERROR) + return ii; + else + { + msg (M_INFO, "GetInterfaceInfo #2 failed (status=%u) : %s", + (unsigned int)status, + strerror_win32 (status, gc)); + } + } + return ii; +} + +static const IP_ADAPTER_INDEX_MAP * +get_interface_info (DWORD index, struct gc_arena *gc) +{ + const IP_INTERFACE_INFO *list = get_interface_info_list (gc); + if (list) + { + int i; + for (i = 0; i < list->NumAdapters; ++i) + { + const IP_ADAPTER_INDEX_MAP *inter = &list->Adapter[i]; + if (index == inter->Index) + return inter; + } + } + return NULL; +} + +/* + * Given an adapter index, return a pointer to the + * IP_ADAPTER_INFO structure for that adapter. + */ + +static const IP_ADAPTER_INFO * +get_adapter (const IP_ADAPTER_INFO *ai, DWORD index) +{ + if (ai && index != (DWORD)~0) + { + const IP_ADAPTER_INFO *a; + + /* find index in the linked list */ + for (a = ai; a != NULL; a = a->Next) + { + if (a->Index == index) + return a; + } + } + return NULL; +} + +static const IP_ADAPTER_INFO * +get_adapter_info (DWORD index, struct gc_arena *gc) +{ + return get_adapter (get_adapter_info_list (gc), index); +} + +static int +get_adapter_n_ip_netmask (const IP_ADAPTER_INFO *ai) +{ + if (ai) + { + int n = 0; + const IP_ADDR_STRING *ip = &ai->IpAddressList; + + while (ip) + { + ++n; + ip = ip->Next; + } + return n; + } + else + return 0; +} + +static bool +get_adapter_ip_netmask (const IP_ADAPTER_INFO *ai, const int n, in_addr_t *ip, in_addr_t *netmask) +{ + bool ret = false; + *ip = 0; + *netmask = 0; + + if (ai) + { + const IP_ADDR_STRING *iplist = &ai->IpAddressList; + int i = 0; + + while (iplist) + { + if (i == n) + break; + ++i; + iplist = iplist->Next; + } + + if (iplist) + { + const unsigned int getaddr_flags = GETADDR_HOST_ORDER; + const char *ip_str = iplist->IpAddress.String; + const char *netmask_str = iplist->IpMask.String; + bool succeed1 = false; + bool succeed2 = false; + + if (ip_str && netmask_str && strlen (ip_str) && strlen (netmask_str)) + { + *ip = getaddr (getaddr_flags, ip_str, 0, &succeed1, NULL); + *netmask = getaddr (getaddr_flags, netmask_str, 0, &succeed2, NULL); + ret = (succeed1 == true && succeed2 == true); + } + } + } + + return ret; +} + +const IP_ADAPTER_INFO * +get_tun_adapter (const struct tuntap *tt, const IP_ADAPTER_INFO *list) +{ + if (list && tt) + return get_adapter (list, tt->adapter_index); + else + return NULL; +} + +bool +is_adapter_up (const struct tuntap *tt, const IP_ADAPTER_INFO *list) +{ + int i; + bool ret = false; + + const IP_ADAPTER_INFO *ai = get_tun_adapter (tt, list); + + if (ai) + { + const int n = get_adapter_n_ip_netmask (ai); + + /* loop once for every IP/netmask assigned to adapter */ + for (i = 0; i < n; ++i) + { + in_addr_t ip, netmask; + if (get_adapter_ip_netmask (ai, i, &ip, &netmask)) + { + if (tt->local && tt->adapter_netmask) + { + /* wait for our --ifconfig parms to match the actual adapter parms */ + if (tt->local == ip && tt->adapter_netmask == netmask) + ret = true; + } + else + { + /* --ifconfig was not defined, maybe using a real DHCP server */ + if (ip && netmask) + ret = true; + } + } + } + } + else + ret = true; /* this can occur when TAP adapter is bridged */ + + return ret; +} + +bool +is_ip_in_adapter_subnet (const IP_ADAPTER_INFO *ai, const in_addr_t ip, in_addr_t *highest_netmask) +{ + int i; + bool ret = false; + + if (highest_netmask) + *highest_netmask = 0; + + if (ai) + { + const int n = get_adapter_n_ip_netmask (ai); + for (i = 0; i < n; ++i) + { + in_addr_t adapter_ip, adapter_netmask; + if (get_adapter_ip_netmask (ai, i, &adapter_ip, &adapter_netmask)) + { + if (adapter_ip && adapter_netmask && (ip & adapter_netmask) == (adapter_ip & adapter_netmask)) + { + if (highest_netmask && adapter_netmask > *highest_netmask) + *highest_netmask = adapter_netmask; + ret = true; + } + } + } + } + return ret; +} + +DWORD +adapter_index_of_ip (const IP_ADAPTER_INFO *list, const in_addr_t ip, int *count) +{ + struct gc_arena gc = gc_new (); + DWORD ret = ~0; + in_addr_t highest_netmask = 0; + bool first = true; + + if (count) + *count = 0; + + while (list) + { + in_addr_t hn; + + if (is_ip_in_adapter_subnet (list, ip, &hn)) + { + if (first || hn > highest_netmask) + { + highest_netmask = hn; + if (count) + *count = 1; + ret = list->Index; + first = false; + } + else if (hn == highest_netmask) + { + if (count) + ++*count; + } + } + list = list->Next; + } + + dmsg (D_ROUTE_DEBUG, "DEBUG: IP Locate: ip=%s nm=%s index=%d count=%d", + print_in_addr_t (ip, 0, &gc), + print_in_addr_t (highest_netmask, 0, &gc), + (int)ret, + count ? *count : -1); + + if (ret == ~0 && count) + *count = 0; + + gc_free (&gc); + return ret; +} + +/* + * Given an adapter index, return true if the adapter + * is DHCP disabled. + */ +static bool +dhcp_disabled (DWORD index) +{ + struct gc_arena gc = gc_new (); + const IP_ADAPTER_INFO *ai = get_adapter_info (index, &gc); + bool ret = false; + + if (ai && !ai->DhcpEnabled) + ret = true; + + gc_free (&gc); + return ret; +} + +/* + * Delete all temporary address/netmask pairs which were added + * to adapter (given by index) by previous calls to AddIPAddress. + */ +static void +delete_temp_addresses (DWORD index) +{ + struct gc_arena gc = gc_new (); + const IP_ADAPTER_INFO *a = get_adapter_info (index, &gc); + + if (a) + { + const IP_ADDR_STRING *ip = &a->IpAddressList; + while (ip) + { + DWORD status; + const DWORD context = ip->Context; + + if ((status = DeleteIPAddress ((ULONG) context)) == NO_ERROR) + { + msg (M_INFO, "Successfully deleted previously set dynamic IP/netmask: %s/%s", + ip->IpAddress.String, + ip->IpMask.String); + } + else + { + const char *empty = "0.0.0.0"; + if (strcmp (ip->IpAddress.String, empty) + || strcmp (ip->IpMask.String, empty)) + msg (M_INFO, "NOTE: could not delete previously set dynamic IP/netmask: %s/%s (status=%u)", + ip->IpAddress.String, + ip->IpMask.String, + (unsigned int)status); + } + ip = ip->Next; + } + } + gc_free (&gc); +} + +/* + * Get interface index for use with IP Helper API functions. + */ +static DWORD +get_interface_index (const char *guid) +{ + struct gc_arena gc = gc_new (); + ULONG index; + DWORD status; + wchar_t wbuf[256]; + snwprintf (wbuf, SIZE (wbuf), L"\\DEVICE\\TCPIP_%S", guid); + wbuf [SIZE(wbuf) - 1] = 0; + if ((status = GetAdapterIndex (wbuf, &index)) != NO_ERROR) + { + msg (M_INFO, "NOTE: could not get adapter index for %S, status=%u : %s", + wbuf, + (unsigned int)status, + strerror_win32 (status, &gc)); + gc_free (&gc); + return (DWORD)~0; + } + else + { + gc_free (&gc); + return index; + } +} + +/* + * Return a string representing a PIP_ADDR_STRING + */ +static const char * +format_ip_addr_string (const IP_ADDR_STRING *ip, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (256, gc); + while (ip) + { + buf_printf (&out, "%s", ip->IpAddress.String); + if (strlen (ip->IpMask.String)) + { + buf_printf (&out, "/"); + buf_printf (&out, "%s", ip->IpMask.String); + } + buf_printf (&out, " "); + ip = ip->Next; + } + return BSTR (&out); +} + +/* + * Show info for a single adapter + */ +static void +show_adapter (int msglev, const IP_ADAPTER_INFO *a, struct gc_arena *gc) +{ + msg (msglev, "%s", a->Description); + msg (msglev, " Index = %d", (int)a->Index); + msg (msglev, " GUID = %s", a->AdapterName); + msg (msglev, " IP = %s", format_ip_addr_string (&a->IpAddressList, gc)); + msg (msglev, " MAC = %s", format_hex_ex (a->Address, a->AddressLength, 0, 1, ":", gc)); + msg (msglev, " GATEWAY = %s", format_ip_addr_string (&a->GatewayList, gc)); + if (a->DhcpEnabled) + { + msg (msglev, " DHCP SERV = %s", format_ip_addr_string (&a->DhcpServer, gc)); + msg (msglev, " DHCP LEASE OBTAINED = %s", time_string (a->LeaseObtained, 0, false, gc)); + msg (msglev, " DHCP LEASE EXPIRES = %s", time_string (a->LeaseExpires, 0, false, gc)); + } + if (a->HaveWins) + { + msg (msglev, " PRI WINS = %s", format_ip_addr_string (&a->PrimaryWinsServer, gc)); + msg (msglev, " SEC WINS = %s", format_ip_addr_string (&a->SecondaryWinsServer, gc)); + } +} + +/* + * Show current adapter list + */ +void +show_adapters (int msglev) +{ + struct gc_arena gc = gc_new (); + const IP_ADAPTER_INFO *ai = get_adapter_info_list (&gc); + + msg (msglev, "SYSTEM ADAPTER LIST"); + if (ai) + { + const IP_ADAPTER_INFO *a; + + /* find index in the linked list */ + for (a = ai; a != NULL; a = a->Next) + { + show_adapter (msglev, a, &gc); + } + } + gc_free (&gc); +} + +/* + * DHCP release/renewal + */ + +bool +dhcp_release (const struct tuntap *tt) +{ + struct gc_arena gc = gc_new (); + bool ret = false; + if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ && tt->adapter_index != ~0) + { + const IP_ADAPTER_INDEX_MAP *inter = get_interface_info (tt->adapter_index, &gc); + if (inter) + { + DWORD status = IpReleaseAddress ((IP_ADAPTER_INDEX_MAP *)inter); + if (status == NO_ERROR) + { + msg (D_TUNTAP_INFO, "TAP: DHCP address released"); + ret = true; + } + else + msg (M_WARN, "NOTE: Release of DHCP-assigned IP address lease on TAP-Win32 adapter failed: %s (code=%u)", + strerror_win32 (status, &gc), + (unsigned int)status); + } + } + gc_free (&gc); + return ret; +} + +bool +dhcp_renew (const struct tuntap *tt) +{ + struct gc_arena gc = gc_new (); + bool ret = false; + if (tt && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ && tt->adapter_index != ~0) + { + const IP_ADAPTER_INDEX_MAP *inter = get_interface_info (tt->adapter_index, &gc); + if (inter) + { + DWORD status = IpRenewAddress ((IP_ADAPTER_INDEX_MAP *)inter); + if (status == NO_ERROR) + { + msg (D_TUNTAP_INFO, "TAP: DHCP address renewal succeeded"); + ret = true; + } + else + msg (M_WARN, "WARNING: Failed to renew DHCP IP address lease on TAP-Win32 adapter: %s (code=%u)", + strerror_win32 (status, &gc), + (unsigned int)status); + } + } + gc_free (&gc); + return ret; +} + +/* + * Convert DHCP options from the command line / config file + * into a raw DHCP-format options string. + */ + +static void +write_dhcp_u8 (struct buffer *buf, const int type, const int data) +{ + if (!buf_safe (buf, 3)) + msg (M_FATAL, "write_dhcp_u8: buffer overflow building DHCP options"); + buf_write_u8 (buf, type); + buf_write_u8 (buf, 1); + buf_write_u8 (buf, data); +} + +static void +write_dhcp_u32_array (struct buffer *buf, const int type, const uint32_t *data, const unsigned int len) +{ + if (len > 0) + { + int i; + const int size = len * sizeof (uint32_t); + + if (!buf_safe (buf, 2 + size)) + msg (M_FATAL, "write_dhcp_u32_array: buffer overflow building DHCP options"); + if (size < 1 || size > 255) + msg (M_FATAL, "write_dhcp_u32_array: size (%d) must be > 0 and <= 255", size); + buf_write_u8 (buf, type); + buf_write_u8 (buf, size); + for (i = 0; i < len; ++i) + buf_write_u32 (buf, data[i]); + } +} + +static void +write_dhcp_str (struct buffer *buf, const int type, const char *str) +{ + const int len = strlen (str); + if (!buf_safe (buf, 2 + len)) + msg (M_FATAL, "write_dhcp_str: buffer overflow building DHCP options"); + if (len < 1 || len > 255) + msg (M_FATAL, "write_dhcp_str: string '%s' must be > 0 bytes and <= 255 bytes", str); + buf_write_u8 (buf, type); + buf_write_u8 (buf, len); + buf_write (buf, str, len); +} + +static void +build_dhcp_options_string (struct buffer *buf, const struct tuntap_options *o) +{ + if (o->domain) + write_dhcp_str (buf, 15, o->domain); + + if (o->netbios_scope) + write_dhcp_str (buf, 47, o->netbios_scope); + + if (o->netbios_node_type) + write_dhcp_u8 (buf, 46, o->netbios_node_type); + + write_dhcp_u32_array (buf, 6, (uint32_t*)o->dns, o->dns_len); + write_dhcp_u32_array (buf, 44, (uint32_t*)o->wins, o->wins_len); + write_dhcp_u32_array (buf, 42, (uint32_t*)o->ntp, o->ntp_len); + write_dhcp_u32_array (buf, 45, (uint32_t*)o->nbdd, o->nbdd_len); + + /* the MS DHCP server option 'Disable Netbios-over-TCP/IP + is implemented as vendor option 001, value 002. + A value of 001 means 'leave NBT alone' which is the default */ + if (o->disable_nbt) + { + buf_write_u8 (buf, 43); + buf_write_u8 (buf, 6); /* total length field */ + buf_write_u8 (buf, 0x001); + buf_write_u8 (buf, 4); /* length of the vendor specified field */ + buf_write_u32 (buf, 0x002); + } +} + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + struct gc_arena gc = gc_new (); + char device_path[256]; + const char *device_guid = NULL; + DWORD len; + + /*netcmd_semaphore_lock ();*/ + + ipv6_support (ipv6, false, tt); + + if (tt->type == DEV_TYPE_NULL) + { + open_null (tt); + gc_free (&gc); + return; + } + else if (tt->type == DEV_TYPE_TAP || tt->type == DEV_TYPE_TUN) + { + ; + } + else + { + msg (M_FATAL|M_NOPREFIX, "Unknown virtual device type: '%s'", dev); + } + + /* + * Lookup the device name in the registry, using the --dev-node high level name. + */ + { + const struct tap_reg *tap_reg = get_tap_reg (&gc); + const struct panel_reg *panel_reg = get_panel_reg (&gc); + char guid_buffer[256]; + + at_least_one_tap_win32 (tap_reg); + + if (dev_node) + { + /* Get the device GUID for the device specified with --dev-node. */ + device_guid = get_device_guid (dev_node, guid_buffer, sizeof (guid_buffer), tap_reg, panel_reg, &gc); + + if (!device_guid) + msg (M_FATAL, "TAP-Win32 adapter '%s' not found", dev_node); + + /* Open Windows TAP-Win32 adapter */ + openvpn_snprintf (device_path, sizeof(device_path), "%s%s%s", + USERMODEDEVICEDIR, + device_guid, + TAPSUFFIX); + + tt->hand = CreateFile ( + device_path, + GENERIC_READ | GENERIC_WRITE, + 0, /* was: FILE_SHARE_READ */ + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 + ); + + if (tt->hand == INVALID_HANDLE_VALUE) + msg (M_ERR, "CreateFile failed on TAP device: %s", device_path); + } + else + { + int device_number = 0; + + /* Try opening all TAP devices until we find one available */ + while (true) + { + device_guid = get_unspecified_device_guid (device_number, + guid_buffer, + sizeof (guid_buffer), + tap_reg, + panel_reg, + &gc); + + if (!device_guid) + msg (M_FATAL, "All TAP-Win32 adapters on this system are currently in use."); + + /* Open Windows TAP-Win32 adapter */ + openvpn_snprintf (device_path, sizeof(device_path), "%s%s%s", + USERMODEDEVICEDIR, + device_guid, + TAPSUFFIX); + + tt->hand = CreateFile ( + device_path, + GENERIC_READ | GENERIC_WRITE, + 0, /* was: FILE_SHARE_READ */ + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, + 0 + ); + + if (tt->hand == INVALID_HANDLE_VALUE) + msg (D_TUNTAP_INFO, "CreateFile failed on TAP device: %s", device_path); + else + break; + + device_number++; + } + } + + /* translate high-level device name into a device instance + GUID using the registry */ + tt->actual_name = string_alloc (guid_buffer, NULL); + } + + msg (M_INFO, "TAP-WIN32 device [%s] opened: %s", tt->actual_name, device_path); + + /* get driver version info */ + { + ULONG info[3]; + CLEAR (info); + if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_VERSION, + &info, sizeof (info), + &info, sizeof (info), &len, NULL)) + { + msg (D_TUNTAP_INFO, "TAP-Win32 Driver Version %d.%d %s", + (int) info[0], + (int) info[1], + (info[2] ? "(DEBUG)" : "")); + + } + if ( !(info[0] > TAP_WIN32_MIN_MAJOR + || (info[0] == TAP_WIN32_MIN_MAJOR && info[1] >= TAP_WIN32_MIN_MINOR)) ) + msg (M_FATAL, "ERROR: This version of " PACKAGE_NAME " requires a TAP-Win32 driver that is at least version %d.%d -- If you recently upgraded your " PACKAGE_NAME " distribution, a reboot is probably required at this point to get Windows to see the new driver.", + TAP_WIN32_MIN_MAJOR, + TAP_WIN32_MIN_MINOR); + } + + /* get driver MTU */ + { + ULONG mtu; + if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_MTU, + &mtu, sizeof (mtu), + &mtu, sizeof (mtu), &len, NULL)) + { + tt->post_open_mtu = (int) mtu; + msg (D_MTU_INFO, "TAP-Win32 MTU=%d", (int) mtu); + } + } + + /* set point-to-point mode if TUN device */ + + if (tt->type == DEV_TYPE_TUN) + { + in_addr_t ep[2]; + ep[0] = htonl (tt->local); + ep[1] = htonl (tt->remote_netmask); + if (!tt->did_ifconfig_setup) + { + msg (M_FATAL, "ERROR: --dev tun also requires --ifconfig"); + } + if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_POINT_TO_POINT, + ep, sizeof (ep), + ep, sizeof (ep), &len, NULL)) + msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a DeviceIoControl call to set Point-to-Point mode, which is required for --dev tun"); + } + + /* should we tell the TAP-Win32 driver to masquerade as a DHCP server as a means + of setting the adapter address? */ + if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ) + { + uint32_t ep[4]; + + /* We will answer DHCP requests with a reply to set IP/subnet to these values */ + ep[0] = htonl (tt->local); + ep[1] = htonl (tt->adapter_netmask); + + /* At what IP address should the DHCP server masquerade at? */ + if (tt->type == DEV_TYPE_TUN) + { + ep[2] = htonl (tt->remote_netmask); + if (tt->options.dhcp_masq_custom_offset) + msg (M_WARN, "WARNING: because you are using '--dev tun' mode, the '--ip-win32 dynamic [offset]' option is ignoring the offset parameter"); + } + else + { + in_addr_t dsa; /* DHCP server addr */ + + ASSERT (tt->type == DEV_TYPE_TAP); + + if (tt->options.dhcp_masq_offset < 0) + dsa = (tt->local | (~tt->adapter_netmask)) + tt->options.dhcp_masq_offset; + else + dsa = (tt->local & tt->adapter_netmask) + tt->options.dhcp_masq_offset; + + if (dsa == tt->local) + msg (M_FATAL, "ERROR: There is a clash between the --ifconfig local address and the internal DHCP server address -- both are set to %s -- please use the --ip-win32 dynamic option to choose a different free address from the --ifconfig subnet for the internal DHCP server", print_in_addr_t (dsa, 0, &gc)); + + if ((tt->local & tt->adapter_netmask) != (dsa & tt->adapter_netmask)) + msg (M_FATAL, "ERROR: --tap-win32 dynamic [offset] : offset is outside of --ifconfig subnet"); + + ep[2] = htonl (dsa); + } + + /* lease time in seconds */ + ep[3] = (uint32_t) tt->options.dhcp_lease_time; + + ASSERT (ep[3] > 0); + + if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_DHCP_MASQ, + ep, sizeof (ep), + ep, sizeof (ep), &len, NULL)) + msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a DeviceIoControl call to set TAP_IOCTL_CONFIG_DHCP_MASQ mode"); + + msg (M_INFO, "Notified TAP-Win32 driver to set a DHCP IP/netmask of %s/%s on interface %s [DHCP-serv: %s, lease-time: %d]", + print_in_addr_t (tt->local, 0, &gc), + print_in_addr_t (tt->adapter_netmask, 0, &gc), + device_guid, + print_in_addr_t (ep[2], IA_NET_ORDER, &gc), + ep[3] + ); + + /* user-supplied DHCP options capability */ + if (tt->options.dhcp_options) + { + struct buffer buf = alloc_buf (256); + build_dhcp_options_string (&buf, &tt->options); + msg (D_DHCP_OPT, "DHCP option string: %s", format_hex (BPTR (&buf), BLEN (&buf), 0, &gc)); + if (!DeviceIoControl (tt->hand, TAP_IOCTL_CONFIG_DHCP_SET_OPT, + BPTR (&buf), BLEN (&buf), + BPTR (&buf), BLEN (&buf), &len, NULL)) + msg (M_FATAL, "ERROR: The TAP-Win32 driver rejected a TAP_IOCTL_CONFIG_DHCP_SET_OPT DeviceIoControl call"); + free_buf (&buf); + } + } + + /* set driver media status to 'connected' */ + { + ULONG status = TRUE; + if (!DeviceIoControl (tt->hand, TAP_IOCTL_SET_MEDIA_STATUS, + &status, sizeof (status), + &status, sizeof (status), &len, NULL)) + msg (M_WARN, "WARNING: The TAP-Win32 driver rejected a TAP_IOCTL_SET_MEDIA_STATUS DeviceIoControl call."); + } + + /* possible wait for adapter to come up */ + { + int s = tt->options.tap_sleep; + if (s > 0) + { + msg (M_INFO, "Sleeping for %d seconds...", s); + openvpn_sleep (s); + } + } + + /* possibly use IP Helper API to set IP address on adapter */ + { + DWORD index = get_interface_index (device_guid); + tt->adapter_index = index; + + /* flush arp cache */ + if (index != (DWORD)~0) + { + DWORD status; + + if ((status = FlushIpNetTable (index)) == NO_ERROR) + msg (M_INFO, "Successful ARP Flush on interface [%u] %s", + (unsigned int)index, + device_guid); + else + msg (M_WARN, "NOTE: FlushIpNetTable failed on interface [%u] %s (status=%u) : %s", + (unsigned int)index, + device_guid, + (unsigned int)status, + strerror_win32 (status, &gc)); + } + + /* + * If the TAP-Win32 driver is masquerading as a DHCP server + * make sure the TCP/IP properties for the adapter are + * set correctly. + */ + if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_DHCP_MASQ) + { + /* check dhcp enable status */ + if (dhcp_disabled (index)) + msg (M_WARN, "WARNING: You have selected '--ip-win32 dynamic', which will not work unless the TAP-Win32 TCP/IP properties are set to 'Obtain an IP address automatically'"); + + /* force an explicit DHCP lease renewal on TAP adapter? */ + if (tt->options.dhcp_pre_release) + dhcp_release (tt); + if (tt->options.dhcp_renew) + dhcp_renew (tt); + } + + if (tt->did_ifconfig_setup && tt->options.ip_win32_type == IPW32_SET_IPAPI) + { + DWORD status; + const char *error_suffix = "I am having trouble using the Windows 'IP helper API' to automatically set the IP address -- consider using other --ip-win32 methods (not 'ipapi')"; + + /* couldn't get adapter index */ + if (index == (DWORD)~0) + { + msg (M_FATAL, "ERROR: unable to get adapter index for interface %s -- %s", + device_guid, + error_suffix); + } + + /* check dhcp enable status */ + if (dhcp_disabled (index)) + msg (M_WARN, "NOTE: You have selected (explicitly or by default) '--ip-win32 ipapi', which has a better chance of working correctly if the TAP-Win32 TCP/IP properties are set to 'Obtain an IP address automatically'"); + + /* delete previously added IP addresses which were not + correctly deleted */ + delete_temp_addresses (index); + + /* add a new IP address */ + if ((status = AddIPAddress (htonl(tt->local), + htonl(tt->adapter_netmask), + index, + &tt->ipapi_context, + &tt->ipapi_instance)) == NO_ERROR) + msg (M_INFO, "Succeeded in adding a temporary IP/netmask of %s/%s to interface %s using the Win32 IP Helper API", + print_in_addr_t (tt->local, 0, &gc), + print_in_addr_t (tt->adapter_netmask, 0, &gc), + device_guid + ); + else + msg (M_FATAL, "ERROR: AddIPAddress %s/%s failed on interface %s, index=%d, status=%u (windows error: '%s') -- %s", + print_in_addr_t (tt->local, 0, &gc), + print_in_addr_t (tt->adapter_netmask, 0, &gc), + device_guid, + (int)index, + (unsigned int)status, + strerror_win32 (status, &gc), + error_suffix); + tt->ipapi_context_defined = true; + } + } + /*netcmd_semaphore_release ();*/ + gc_free (&gc); +} + +const char * +tap_win32_getinfo (const struct tuntap *tt, struct gc_arena *gc) +{ + if (tt && tt->hand != NULL) + { + struct buffer out = alloc_buf_gc (256, gc); + DWORD len; + if (DeviceIoControl (tt->hand, TAP_IOCTL_GET_INFO, + BSTR (&out), BCAP (&out), + BSTR (&out), BCAP (&out), + &len, NULL)) + { + return BSTR (&out); + } + } + return NULL; +} + +void +tun_show_debug (struct tuntap *tt) +{ + if (tt && tt->hand != NULL) + { + struct buffer out = alloc_buf (1024); + DWORD len; + while (DeviceIoControl (tt->hand, TAP_IOCTL_GET_LOG_LINE, + BSTR (&out), BCAP (&out), + BSTR (&out), BCAP (&out), + &len, NULL)) + { + msg (D_TAP_WIN32_DEBUG, "TAP-Win32: %s", BSTR (&out)); + } + free_buf (&out); + } +} + +void +close_tun (struct tuntap *tt) +{ + struct gc_arena gc = gc_new (); + + if (tt) + { +#if 1 + if (tt->ipapi_context_defined) + { + DWORD status; + if ((status = DeleteIPAddress (tt->ipapi_context)) != NO_ERROR) + { + msg (M_WARN, "Warning: DeleteIPAddress[%u] failed on TAP-Win32 adapter, status=%u : %s", + (unsigned int)tt->ipapi_context, + (unsigned int)status, + strerror_win32 (status, &gc)); + } + } +#endif + + if (tt->options.dhcp_release) + dhcp_release (tt); + + if (tt->hand != NULL) + { + dmsg (D_WIN32_IO_LOW, "Attempting CancelIO on TAP-Win32 adapter"); + if (!CancelIo (tt->hand)) + msg (M_WARN | M_ERRNO, "Warning: CancelIO failed on TAP-Win32 adapter"); + } + + dmsg (D_WIN32_IO_LOW, "Attempting close of overlapped read event on TAP-Win32 adapter"); + overlapped_io_close (&tt->reads); + + dmsg (D_WIN32_IO_LOW, "Attempting close of overlapped write event on TAP-Win32 adapter"); + overlapped_io_close (&tt->writes); + + if (tt->hand != NULL) + { + dmsg (D_WIN32_IO_LOW, "Attempting CloseHandle on TAP-Win32 adapter"); + if (!CloseHandle (tt->hand)) + msg (M_WARN | M_ERRNO, "Warning: CloseHandle failed on TAP-Win32 adapter"); + } + + if (tt->actual_name) + free (tt->actual_name); + + clear_tuntap (tt); + free (tt); + } + gc_free (&gc); +} + +/* + * Convert --ip-win32 constants between index and ascii form. + */ + +struct ipset_names { + const char *short_form; +}; + +/* Indexed by IPW32_SET_x */ +static const struct ipset_names ipset_names[] = { + {"manual"}, + {"netsh"}, + {"ipapi"}, + {"dynamic"} +}; + +int +ascii2ipset (const char* name) +{ + int i; + ASSERT (IPW32_SET_N == SIZE (ipset_names)); + for (i = 0; i < IPW32_SET_N; ++i) + if (!strcmp (name, ipset_names[i].short_form)) + return i; + return -1; +} + +const char * +ipset2ascii (int index) +{ + ASSERT (IPW32_SET_N == SIZE (ipset_names)); + if (index < 0 || index >= IPW32_SET_N) + return "[unknown --ip-win32 type]"; + else + return ipset_names[index].short_form; +} + +const char * +ipset2ascii_all (struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (256, gc); + int i; + + ASSERT (IPW32_SET_N == SIZE (ipset_names)); + for (i = 0; i < IPW32_SET_N; ++i) + { + if (i) + buf_printf(&out, " "); + buf_printf(&out, "[%s]", ipset2ascii(i)); + } + return BSTR (&out); +} + +#else /* generic */ + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); +} + +void +close_tun (struct tuntap* tt) +{ + if (tt) + { + close_tun_generic (tt); + free (tt); + } +} + +int +write_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + return write (tt->fd, buf, len); +} + +int +read_tun (struct tuntap* tt, uint8_t *buf, int len) +{ + return read (tt->fd, buf, len); +} + +#endif |