X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=tun.c;h=5c0417764b749c91945813d58c0e87fa809b77c7;hb=438d487626a37f1d4e5c1430328220bce5efb35e;hp=a5813272c43c0e662fa881d3e45c612b134bd9bd;hpb=0daaad0254b6f95d95b9f4de8f701d11231c2d46;p=platform%2Fupstream%2Fopenconnect.git diff --git a/tun.c b/tun.c index a581327..5c04177 100644 --- a/tun.c +++ b/tun.c @@ -1,7 +1,7 @@ /* * OpenConnect (SSL + DTLS) VPN client * - * Copyright © 2008-2010 Intel Corporation. + * Copyright © 2008-2012 Intel Corporation. * * Author: David Woodhouse * @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,8 @@ #include #include #include +#include +#include #if defined(__sun__) #include #include @@ -47,7 +50,7 @@ #endif #endif -#include "openconnect.h" +#include "openconnect-internal.h" /* * If an if_tun.h include file was found anywhere (by the Makefile), it's @@ -75,54 +78,30 @@ #define TUN_HAS_AF_PREFIX 1 #endif -#ifdef __sun__ -static int local_config_tun(struct openconnect_info *vpninfo, int mtu_only) -{ - if (!mtu_only) - vpninfo->progress(vpninfo, PRG_ERR, - "No vpnc-script configured. Need Solaris IP-setting code\n"); - return 0; -} -#else -static int local_config_tun(struct openconnect_info *vpninfo, int mtu_only) +static int set_tun_mtu(struct openconnect_info *vpninfo) { +#ifndef __sun__ /* We don't know how to do this on Solaris */ struct ifreq ifr; int net_fd; net_fd = socket(PF_INET, SOCK_DGRAM, 0); if (net_fd < 0) { - perror("open net"); + perror(_("open net")); return -EINVAL; } + memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, vpninfo->ifname, sizeof(ifr.ifr_name) - 1); + ifr.ifr_mtu = vpninfo->actual_mtu; - if (!mtu_only) { - struct sockaddr_in addr; - - if (ioctl(net_fd, SIOCGIFFLAGS, &ifr) < 0) - perror("SIOCGIFFLAGS"); - - ifr.ifr_flags |= IFF_UP | IFF_POINTOPOINT; - if (ioctl(net_fd, SIOCSIFFLAGS, &ifr) < 0) - perror("SIOCSIFFLAGS"); - - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = inet_addr(vpninfo->vpn_addr); - memcpy(&ifr.ifr_addr, &addr, sizeof(addr)); - if (ioctl(net_fd, SIOCSIFADDR, &ifr) < 0) - perror("SIOCSIFADDR"); - } - - ifr.ifr_mtu = vpninfo->mtu; if (ioctl(net_fd, SIOCSIFMTU, &ifr) < 0) - perror("SIOCSIFMTU"); + perror(_("SIOCSIFMTU")); close(net_fd); - +#endif return 0; } -#endif + static int setenv_int(const char *opt, int value) { @@ -143,19 +122,25 @@ static int netmasklen(struct in_addr addr) } static int process_split_xxclude(struct openconnect_info *vpninfo, - char *in_ex, char *route, int *v4_incs, + int include, const char *route, int *v4_incs, int *v6_incs) { struct in_addr addr; + const char *in_ex = include?"IN":"EX"; char envname[80]; char *slash; slash = strchr(route, '/'); if (!slash) { badinc: - vpninfo->progress(vpninfo, PRG_ERR, - "Discard bad split %sclude: \"%s\"\n", - in_ex, route); + if (include) + vpn_progress(vpninfo, PRG_ERR, + _("Discard bad split include: \"%s\"\n"), + route); + else + vpn_progress(vpninfo, PRG_ERR, + _("Discard bad split exclude: \"%s\"\n"), + route); return -EINVAL; } @@ -242,7 +227,7 @@ static void set_banner(struct openconnect_info *vpninfo) char *banner, *q; const char *p; - if (!vpninfo->banner || !(banner = malloc(strlen(vpninfo->banner)))) { + if (!vpninfo->banner || !(banner = malloc(strlen(vpninfo->banner)+1))) { unsetenv("CISCO_BANNER"); return; } @@ -250,7 +235,8 @@ static void set_banner(struct openconnect_info *vpninfo) q = banner; while (*p) { - if (*p == '%' && isxdigit(p[1]) && isxdigit(p[2])) { + if (*p == '%' && isxdigit((int)(unsigned char)p[1]) && + isxdigit((int)(unsigned char)p[2])) { *(q++) = unhex(p + 1); p += 3; } else @@ -270,12 +256,11 @@ static void set_script_env(struct openconnect_info *vpninfo) if (!ret) setenv("VPNGATEWAY", host, 1); - setenv("reason", "connect", 1); set_banner(vpninfo); unsetenv("CISCO_SPLIT_INC"); unsetenv("CISCO_SPLIT_EXC"); - setenv_int("INTERNAL_IP4_MTU", vpninfo->mtu); + setenv_int("INTERNAL_IP4_MTU", vpninfo->actual_mtu); if (vpninfo->vpn_addr) { setenv("INTERNAL_IP4_ADDRESS", vpninfo->vpn_addr, 1); @@ -326,13 +311,39 @@ static void set_script_env(struct openconnect_info *vpninfo) if (vpninfo->vpn_proxy_pac) setenv("CISCO_PROXY_PAC", vpninfo->vpn_proxy_pac, 1); + if (vpninfo->split_dns) { + char *list; + int len = 0; + struct split_include *dns = vpninfo->split_dns; + + while (dns) { + len += strlen(dns->route) + 1; + dns = dns->next; + } + list = malloc(len); + if (list) { + char *p = list; + + dns = vpninfo->split_dns; + while (1) { + strcpy(p, dns->route); + p += strlen(p); + dns = dns->next; + if (!dns) + break; + *(p++) = ','; + } + setenv("CISCO_SPLIT_DNS", list, 1); + free(list); + } + } if (vpninfo->split_includes) { struct split_include *this = vpninfo->split_includes; int nr_split_includes = 0; int nr_v6_split_includes = 0; while (this) { - process_split_xxclude(vpninfo, "IN", this->route, + process_split_xxclude(vpninfo, 1, this->route, &nr_split_includes, &nr_v6_split_includes); this = this->next; @@ -348,7 +359,7 @@ static void set_script_env(struct openconnect_info *vpninfo) int nr_v6_split_excludes = 0; while (this) { - process_split_xxclude(vpninfo, "EX", this->route, + process_split_xxclude(vpninfo, 0, this->route, &nr_split_excludes, &nr_v6_split_excludes); this = this->next; @@ -361,18 +372,264 @@ static void set_script_env(struct openconnect_info *vpninfo) setenv_cstp_opts(vpninfo); } -static int script_config_tun(struct openconnect_info *vpninfo) +int script_config_tun(struct openconnect_info *vpninfo, const char *reason) { - if (system(vpninfo->vpnc_script)) { + int ret; + + if (!vpninfo->vpnc_script || vpninfo->script_tun) + return 0; + + setenv("reason", reason, 1); + ret = system(vpninfo->vpnc_script); + if (ret == -1) { int e = errno; - vpninfo->progress(vpninfo, PRG_ERR, - "Failed to spawn script '%s': %s\n", - vpninfo->vpnc_script, strerror(e)); + vpn_progress(vpninfo, PRG_ERR, + _("Failed to spawn script '%s' for %s: %s\n"), + vpninfo->vpnc_script, reason, strerror(e)); return -e; } + if (!WIFEXITED(ret)) { + vpn_progress(vpninfo, PRG_ERR, + _("Script '%s' exited abnormally (%x)\n"), + vpninfo->vpnc_script, ret); + return -EIO; + } + ret = WEXITSTATUS(ret); + if (ret) { + vpn_progress(vpninfo, PRG_ERR, + _("Script '%s' returned error %d\n"), + vpninfo->vpnc_script, ret); + return -EIO; + } return 0; } +#ifdef __sun__ +static int link_proto(int unit_nr, const char *devname, uint64_t flags) +{ + int ip_fd, mux_id, tun2_fd; + struct lifreq ifr; + + tun2_fd = open("/dev/tun", O_RDWR); + if (tun2_fd < 0) { + perror(_("Could not open /dev/tun for plumbing")); + return -EIO; + } + if (ioctl(tun2_fd, I_PUSH, "ip") < 0) { + perror(_("Can't push IP")); + close(tun2_fd); + return -EIO; + } + + sprintf(ifr.lifr_name, "tun%d", unit_nr); + ifr.lifr_ppa = unit_nr; + ifr.lifr_flags = flags; + + if (ioctl(tun2_fd, SIOCSLIFNAME, &ifr) < 0) { + perror(_("Can't set ifname")); + close(tun2_fd); + return -1; + } + + ip_fd = open(devname, O_RDWR); + if (ip_fd < 0) { + fprintf(stderr, _("Can't open %s: %s"), devname, + strerror(errno)); + close(tun2_fd); + return -1; + } + + mux_id = ioctl(ip_fd, I_LINK, tun2_fd); + if (mux_id < 0) { + fprintf(stderr, _("Can't plumb %s for IPv%d: %s\n"), + ifr.lifr_name, (flags == IFF_IPV4) ? 4 : 6, + strerror(errno)); + close(tun2_fd); + close(ip_fd); + return -1; + } + + close(tun2_fd); + + return ip_fd; +} +#endif + +#ifdef SIOCIFCREATE +static int bsd_open_tun(char *tun_name) +{ + int fd; + int s; + struct ifreq ifr; + + fd = open(tun_name, O_RDWR); + if (fd >= 0) { + return fd; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, tun_name + 5, sizeof(ifr.ifr_name) - 1); + if (!ioctl(s, SIOCIFCREATE, &ifr)) + fd = open(tun_name, O_RDWR); + + close(s); + } + return fd; +} +#else +#define bsd_open_tun(tun_name) open(tun_name, O_RDWR) +#endif + +static int os_setup_tun(struct openconnect_info *vpninfo) +{ + int tun_fd = -1; + +#ifdef IFF_TUN /* Linux */ + struct ifreq ifr; + int tunerr; + + tun_fd = open("/dev/net/tun", O_RDWR); + if (tun_fd < 0) { + /* Android has /dev/tun instead of /dev/net/tun + Since other systems might have too, just try it + as a fallback instead of using ifdef __ANDROID__ */ + tunerr = errno; + tun_fd = open("/dev/tun", O_RDWR); + } + if (tun_fd < 0) { + /* If the error on /dev/tun is ENOENT, that's boring. + Use the error we got on /dev/net/tun instead */ + if (errno != ENOENT) + tunerr = errno; + + vpn_progress(vpninfo, PRG_ERR, + _("Failed to open tun device: %s\n"), + strerror(tunerr)); + exit(1); + } + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + if (vpninfo->ifname) + strncpy(ifr.ifr_name, vpninfo->ifname, + sizeof(ifr.ifr_name) - 1); + if (ioctl(tun_fd, TUNSETIFF, (void *) &ifr) < 0) { + vpn_progress(vpninfo, PRG_ERR, + _("TUNSETIFF failed: %s\n"), + strerror(errno)); + exit(1); + } + if (!vpninfo->ifname) + vpninfo->ifname = strdup(ifr.ifr_name); +#elif defined (__sun__) + static char tun_name[80]; + int unit_nr; + + tun_fd = open("/dev/tun", O_RDWR); + if (tun_fd < 0) { + perror(_("open /dev/tun")); + return -EIO; + } + + unit_nr = ioctl(tun_fd, TUNNEWPPA, -1); + if (unit_nr < 0) { + perror(_("Failed to create new tun")); + close(tun_fd); + return -EIO; + } + + if (ioctl(tun_fd, I_SRDOPT, RMSGD) < 0) { + perror(_("Failed to put tun file descriptor into message-discard mode")); + close(tun_fd); + return -EIO; + } + + sprintf(tun_name, "tun%d", unit_nr); + vpninfo->ifname = strdup(tun_name); + + vpninfo->ip_fd = link_proto(unit_nr, "/dev/udp", IFF_IPV4); + if (vpninfo->ip_fd < 0) { + close(tun_fd); + return -EIO; + } + + if (vpninfo->vpn_addr6) { + vpninfo->ip6_fd = link_proto(unit_nr, "/dev/udp6", IFF_IPV6); + if (vpninfo->ip6_fd < 0) { + close(tun_fd); + close(vpninfo->ip_fd); + vpninfo->ip_fd = -1; + return -EIO; + } + } else + vpninfo->ip6_fd = -1; + +#else /* BSD et al have /dev/tun$x devices */ + static char tun_name[80]; + int i; + + if (vpninfo->ifname) { + char *endp = NULL; + if (strncmp(vpninfo->ifname, "tun", 3) || + ((void)strtol(vpninfo->ifname + 3, &endp, 10), !endp) || + *endp) { + vpn_progress(vpninfo, PRG_ERR, + _("Invalid interface name '%s'; must match 'tun%%d'\n"), + vpninfo->ifname); + return -EINVAL; + } + snprintf(tun_name, sizeof(tun_name), + "/dev/%s", vpninfo->ifname); + tun_fd = bsd_open_tun(tun_name); + if (tun_fd < 0) { + int err = errno; + vpn_progress(vpninfo, PRG_ERR, + _("Cannot open '%s': %s\n"), + tun_name, strerror(err)); + return -EINVAL; + } + } +#ifdef HAVE_FDEVNAME_R + /* We don't have to iterate over the possible devices; on FreeBSD + at least, opening /dev/tun will give us the next available + device. */ + if (tun_fd < 0) { + tun_fd = open("/dev/tun", O_RDWR); + if (tun_fd >= 0) { + if (!fdevname_r(tun_fd, tun_name, sizeof(tun_name)) || + strncmp(tun_name, "tun", 3)) { + close(tun_fd); + tun_fd = -1; + } else + vpninfo->ifname = strdup(tun_name); + } + } +#endif + if (tun_fd < 0) { + for (i = 0; i < 255; i++) { + sprintf(tun_name, "/dev/tun%d", i); + tun_fd = bsd_open_tun(tun_name); + if (tun_fd >= 0) + break; + } + if (tun_fd < 0) { + perror(_("open tun")); + exit(1); + } + vpninfo->ifname = strdup(tun_name + 5); + } +#ifdef TUNSIFHEAD + i = 1; + if (ioctl(tun_fd, TUNSIFHEAD, &i) < 0) { + perror(_("TUNSIFHEAD")); + exit(1); + } +#endif +#endif + return tun_fd; +} /* Set up a tuntap device. */ int setup_tun(struct openconnect_info *vpninfo) @@ -386,153 +643,38 @@ int setup_tun(struct openconnect_info *vpninfo) int fds[2]; if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fds)) { - perror("socketpair"); + perror(_("socketpair")); exit(1); } tun_fd = fds[0]; child = fork(); if (child < 0) { - perror("fork"); + 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"); + perror(_("execl")); exit(1); } close(fds[1]); vpninfo->script_tun = child; - vpninfo->ifname = "(script)"; + vpninfo->ifname = strdup(_("(script)")); } else { -#ifdef IFF_TUN /* Linux */ - struct ifreq ifr; - - tun_fd = open("/dev/net/tun", O_RDWR); - if (tun_fd < 0) { - vpninfo->progress(vpninfo, PRG_ERR, - "Failed to open tun device: %s\n", - strerror(errno)); - exit(1); - } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_flags = IFF_TUN | IFF_NO_PI; - if (vpninfo->ifname) - strncpy(ifr.ifr_name, vpninfo->ifname, - sizeof(ifr.ifr_name) - 1); - if (ioctl(tun_fd, TUNSETIFF, (void *) &ifr) < 0) { - vpninfo->progress(vpninfo, PRG_ERR, - "TUNSETIFF failed: %s\n", - strerror(errno)); - exit(1); - } - if (!vpninfo->ifname) - vpninfo->ifname = strdup(ifr.ifr_name); -#elif defined (__sun__) - static char tun_name[80]; - int tun2_fd, ip_fd = open("/dev/ip", O_RDWR); - int unit_nr, mux_id; - struct ifreq ifr; - - if (ip_fd < 0) { - perror("open /dev/ip"); - return -EIO; - } - - tun_fd = open("/dev/tun", O_RDWR); - if (tun_fd < 0) { - perror("open /dev/tun"); - close(ip_fd); - return -EIO; - } - - unit_nr = ioctl(tun_fd, TUNNEWPPA, -1); - if (unit_nr < 0) { - perror("Failed to create new tun"); - close(tun_fd); - close(ip_fd); - return -EIO; - } - - tun2_fd = open("/dev/tun", O_RDWR); - if (tun2_fd < 0) { - perror("open /dev/tun again"); - close(tun_fd); - close(ip_fd); - return -EIO; - } - if (ioctl(tun2_fd, I_PUSH, "ip") < 0) { - perror("Can't push IP"); - close(tun2_fd); - close(tun_fd); - close(ip_fd); - return -EIO; - } - if (ioctl(tun2_fd, IF_UNITSEL, &unit_nr) < 0) { - perror("Can't select unit"); - close(tun2_fd); - close(tun_fd); - close(ip_fd); - return -EIO; - } - mux_id = ioctl(ip_fd, I_PLINK, tun2_fd); - if (mux_id < 0) { - perror("Can't link tun to IP"); - close(tun2_fd); - close(tun_fd); - close(ip_fd); - return -EIO; - } - close(tun2_fd); - - sprintf(tun_name, "tun%d", unit_nr); - vpninfo->ifname = tun_name; + script_config_tun(vpninfo, "pre-init"); - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, tun_name); - ifr.ifr_ip_muxid = mux_id; + tun_fd = os_setup_tun(vpninfo); + if (tun_fd < 0) + return tun_fd; - if (ioctl(ip_fd, SIOCSIFMUXID, &ifr) < 0) { - perror("Set mux id"); - close(tun_fd); - ioctl(ip_fd, I_PUNLINK, mux_id); - close(ip_fd); - return -EIO; - } - /* Solaris tunctl needs this in order to tear it down */ - vpninfo->progress(vpninfo, PRG_DEBUG, "mux id is %d\n", mux_id); - vpninfo->tun_muxid = mux_id; - vpninfo->ip_fd = ip_fd; + setenv("TUNDEV", vpninfo->ifname, 1); + script_config_tun(vpninfo, "connect"); -#else /* BSD et al have /dev/tun$x devices */ - static char tun_name[80]; - int i; - for (i = 0; i < 255; i++) { - sprintf(tun_name, "/dev/tun%d", i); - tun_fd = open(tun_name, O_RDWR); - if (tun_fd >= 0) - break; - } - if (tun_fd < 0) { - perror("open tun"); - exit(1); - } - vpninfo->ifname = tun_name + 5; -#ifdef TUNSIFHEAD - i = 1; - if (ioctl(tun_fd, TUNSIFHEAD, &i) < 0) { - perror("TUNSIFHEAD"); - exit(1); - } -#endif -#endif - if (vpninfo->vpnc_script) { - setenv("TUNDEV", vpninfo->ifname, 1); - script_config_tun(vpninfo); - /* We have to set the MTU for ourselves, because the script doesn't */ - local_config_tun(vpninfo, 1); - } else - local_config_tun(vpninfo, 0); + /* Ancient vpnc-scripts might not get this right */ + set_tun_mtu(vpninfo); } fcntl(tun_fd, F_SETFD, FD_CLOEXEC); @@ -549,22 +691,37 @@ int setup_tun(struct openconnect_info *vpninfo) return 0; } +static struct pkt *out_pkt; + int tun_mainloop(struct openconnect_info *vpninfo, int *timeout) { - unsigned char buf[2000]; - int len; int work_done = 0; + int prefix_size = 0; - if (FD_ISSET(vpninfo->tun_fd, &vpninfo->select_rfds)) { - while ((len = read(vpninfo->tun_fd, buf, sizeof(buf))) > 0) { - unsigned char *pkt = buf; #ifdef TUN_HAS_AF_PREFIX - pkt += 4; - len -= 4; + if (!vpninfo->script_tun) + prefix_size = sizeof(int); #endif - if (queue_new_packet(&vpninfo->outgoing_queue, pkt, - len)) + + if (FD_ISSET(vpninfo->tun_fd, &vpninfo->select_rfds)) { + while (1) { + int len = vpninfo->actual_mtu; + + if (!out_pkt) { + out_pkt = malloc(sizeof(struct pkt) + len); + if (!out_pkt) { + vpn_progress(vpninfo, PRG_ERR, "Allocation failed\n"); + break; + } + } + + len = read(vpninfo->tun_fd, out_pkt->data - prefix_size, len + prefix_size); + if (len <= prefix_size) break; + out_pkt->len = len - prefix_size; + + queue_packet(&vpninfo->outgoing_queue, out_pkt); + out_pkt = NULL; work_done = 1; vpninfo->outgoing_qlen++; @@ -586,34 +743,41 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout) int len = this->len; #ifdef TUN_HAS_AF_PREFIX - struct ip *iph = (void *)data; - int type; - - if (iph->ip_v == 6) - type = AF_INET6; - else if (iph->ip_v == 4) - type = AF_INET; - else { - static int complained = 0; - if (!complained) { - complained = 1; - vpninfo->progress(vpninfo, PRG_ERR, - "Unknown packet (len %d) received: %02x %02x %02x %02x...\n", - len, data[0], data[1], data[2], data[3]); + if (!vpninfo->script_tun) { + struct ip *iph = (void *)data; + int type; + + if (iph->ip_v == 6) + type = AF_INET6; + else if (iph->ip_v == 4) + type = AF_INET; + else { + static int complained = 0; + if (!complained) { + complained = 1; + vpn_progress(vpninfo, PRG_ERR, + _("Unknown packet (len %d) received: %02x %02x %02x %02x...\n"), + len, data[0], data[1], data[2], data[3]); + } + free(this); + continue; } - free(this); - continue; + data -= 4; + len += 4; + *(int *)data = htonl(type); } - data -= 4; - len += 4; - *(int *)data = htonl(type); #endif vpninfo->incoming_queue = this->next; - if (write(vpninfo->tun_fd, data, len) < 0 && - errno == ENOTCONN) { - vpninfo->quit_reason = "Client connection terminated"; - return 1; + if (write(vpninfo->tun_fd, data, len) < 0) { + /* Handle death of "script" socket */ + if (vpninfo->script_tun && errno == ENOTCONN) { + vpninfo->quit_reason = "Client connection terminated"; + return 1; + } + vpn_progress(vpninfo, PRG_ERR, + _("Failed to write incoming packet: %s\n"), + strerror(errno)); } free(this); } @@ -624,23 +788,17 @@ int tun_mainloop(struct openconnect_info *vpninfo, int *timeout) void shutdown_tun(struct openconnect_info *vpninfo) { if (vpninfo->script_tun) { - kill(vpninfo->script_tun, SIGHUP); + /* nuke the whole process group */ + kill(-vpninfo->script_tun, SIGHUP); } else { - if (vpninfo->vpnc_script) { - setenv("reason", "disconnect", 1); - if (system(vpninfo->vpnc_script) == -1) { - vpninfo->progress(vpninfo, PRG_ERR, - "Failed to spawn script '%s': %s\n", - vpninfo->vpnc_script, - strerror(errno)); - } - } + script_config_tun(vpninfo, "disconnect"); #ifdef __sun__ - if (ioctl(vpninfo->ip_fd, I_PUNLINK, vpninfo->tun_muxid) < 0) - perror("ioctl(I_PUNLINK)"); - close(vpninfo->ip_fd); vpninfo->ip_fd = -1; + if (vpninfo->ip6_fd != -1) { + close(vpninfo->ip6_fd); + vpninfo->ip6_fd = -1; + } #endif }