From 7a117bd71eb9e67d21faaf456c0c9f495cce3f2b Mon Sep 17 00:00:00 2001 From: Erik Ekman Date: Sun, 28 Jun 2015 20:01:48 +0200 Subject: [PATCH] IPv6 support for DNS traffic in server Server will by default listen on both IPv4 and IPv6. No way to only listen on one protocol right now. Use -L to only listen on a specific v6 address. IP address to use for raw mode is still IPv4 only. Use -n on server to make raw mode work from IPv6 clients, then they will get an IPv4 address from the server for raw mode. Tunnel data is still IPv4. --- CHANGELOG | 2 ++ README.md | 10 ++++--- man/iodine.8 | 38 ++++++++++++++---------- src/common.h | 4 +-- src/iodined.c | 81 ++++++++++++++++++++++++++++++++++++++------------- 5 files changed, 92 insertions(+), 43 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index be995f0..a637541 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,8 @@ master: - Linux: use pkg-config for systemd support flags. Patch by Jason A. Donenfeld. - Change external IP webservice to ipify.org + - Add support for IPv6 in the server. (Raw mode missing) + Traffic inside tunnel is still IPv4. 2014-06-16: 0.7.0 "Kryoptonite" - Partial IPv6 support (#107) diff --git a/README.md b/README.md index 51bc053..9939a5c 100644 --- a/README.md +++ b/README.md @@ -121,14 +121,16 @@ end of the tunnel. In this case, `ping 192.168.99.1` from the iodine client, and ### MISC. INFO #### IPv6 -At the moment the iodined server only supports IPv4. The data inside the tunnel -is IPv4 only. +The data inside the tunnel is IPv4 only. + +The server listens to both IPv4 and IPv6 for incoming requests. Raw mode +currently only works for IPv4, or can use IPv4 from IPv6 login if -n option is +used. The client can use IPv4 or IPv6 nameservers to connect to iodined. The relay nameservers will translate between protocols automatically if needed. Use options `-4` or `-6` to force the client to use a specific IP version for its DNS -queries. The client has to force IPv4 if it has dual-stack connectivity and -the hostname handling the tunnel domain has both `A` and `AAAA` records. +queries. #### Routing It is possible to route all traffic through the DNS tunnel. To do this, first diff --git a/man/iodine.8 b/man/iodine.8 index 5d7b1f6..3a8cea6 100644 --- a/man/iodine.8 +++ b/man/iodine.8 @@ -54,7 +54,9 @@ iodine, iodined \- tunnel IPv4 over DNS .B ] [-m .I mtu .B ] [-l -.I listen_ip +.I listen_ip4 +.B ] [-L +.I listen_ip6 .B ] [-p .I port .B ] [-n @@ -81,9 +83,9 @@ iodine, iodined \- tunnel IPv4 over DNS .I topdomain .SH DESCRIPTION .B iodine -lets you tunnel IPv4 data through a DNS +lets you tunnel IPv4 data through a DNS server. This can be useful in situations where Internet access is firewalled, -but DNS queries are allowed. It needs a TUN/TAP device to operate. The +but DNS queries are allowed. It needs a TUN/TAP device to operate. The bandwidth is asymmetrical, with a measured maximum of 680 kbit/s upstream and 2.3 Mbit/s downstream in a wired LAN test network. @@ -122,7 +124,7 @@ and otherwise tunX. On Mac OS X 10.6, this can also be utunX, which will attempt to use an utun device built into the OS. .TP .B -P password -Use 'password' to authenticate. If not used, +Use 'password' to authenticate. If not used, .B stdin will be used as input. Only the first 32 characters will be used. .TP @@ -245,7 +247,7 @@ rejected, however this will cause problems when requests are routed via a cluster of DNS servers. .TP .B -s -Don't try to configure IP address or MTU. +Don't try to configure IP address or MTU. This should only be used if you have already configured the device that will be used. .TP @@ -261,18 +263,22 @@ This is easily done with : "LC_ALL=C luit iodined \-DD ..." (see luit(1)). .TP .B -m mtu -Set 'mtu' as mtu size for the tun device. +Set 'mtu' as mtu size for the tun device. This will be sent to the client on login, and the client will use the same mtu for its tun device. Default 1130. Note that the DNS traffic will be automatically fragmented when needed. .TP -.B -l listen_ip -Make the server listen only on 'listen_ip' for incoming requests. -By default, incoming requests are accepted from all interfaces. +.B -l listen_ip4 +Make the server listen only on 'listen_ip4' for incoming IPv4 requests. +By default, incoming requests are accepted from all interfaces (0.0.0.0). +.TP +.B -L listen_ip6 +Make the server listen only on 'listen_ip6' for incoming IPv6 requests. +By default, incoming requests are accepted from all interfaces (::) .TP .B -p port -Make the server listen on 'port' instead of 53 for traffic. -If 'listen_ip' does not include localhost, this 'port' can be the same +Make the server listen on 'port' instead of 53 for traffic. +If 'listen_ip4' does not include localhost, this 'port' can be the same as 'dnsport'. .B Note: You must make sure the dns requests are forwarded to this port yourself. @@ -308,7 +314,7 @@ file. .B topdomain The dns traffic will be sent as queries for subdomains under \'topdomain'. This is normally a subdomain to a domain you own. Use a short -domain name to get better throughput. If +domain name to get better throughput. If .B nameserver is the iodined server, then the topdomain can be chosen freely. This argument must be the same on both the client and the server. @@ -316,15 +322,15 @@ must be the same on both the client and the server. .TP .B tunnel_ip[/netmask] This is the server's ip address on the tun interface. The client will be -given the next ip number in the range. It is recommended to use the +given the next ip number in the range. It is recommended to use the 10.0.0.0 or 172.16.0.0 ranges. The default netmask is /27, can be overridden by specifying it here. Using a smaller network will limit the number of concurrent users. .TP .B topdomain The dns traffic is expected to arrive as queries for -subdomains under 'topdomain'. This is normally a subdomain to a domain you -own. Use a short domain name to get better throughput. This argument must be +subdomains under 'topdomain'. This is normally a subdomain to a domain you +own. Use a short domain name to get better throughput. This argument must be the same on both the client and the server. Queries for domains other than 'topdomain' will be forwarded when the \-b option is given, otherwise they will be dropped. @@ -349,7 +355,7 @@ except to the used ssh or vpn ports. If the environment variable .B IODINE_PASS is set, iodine will use the value it is set to as password instead of asking -for one. The +for one. The .B -P option still has precedence. .SS IODINED_PASS diff --git a/src/common.h b/src/common.h index 3dd22b3..a5b3427 100644 --- a/src/common.h +++ b/src/common.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2014 Erik Ekman , + * Copyright (c) 2006-2015 Erik Ekman , * 2006-2009 Bjorn Andersson * * Permission to use, copy, modify, and/or distribute this software for any @@ -97,7 +97,7 @@ struct query { unsigned short id; struct in_addr destination; struct sockaddr_storage from; - int fromlen; + socklen_t fromlen; unsigned short id2; struct sockaddr from2; int fromlen2; diff --git a/src/iodined.c b/src/iodined.c index 8b32b66..aa73a45 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006-2014 Erik Ekman , + * Copyright (c) 2006-2015 Erik Ekman , * 2006-2009 Bjorn Andersson * * Permission to use, copy, modify, and/or distribute this software for any @@ -225,6 +225,14 @@ check_user_and_ip(int userid, struct query *q) received = (struct sockaddr_in *) &(q->from); return memcmp(&(expected->sin_addr), &(received->sin_addr), sizeof(struct in_addr)); } + /* Check IPv6 */ + if (q->from.ss_family == AF_INET6) { + struct sockaddr_in6 *expected, *received; + + expected = (struct sockaddr_in6 *) &(users[userid].host); + received = (struct sockaddr_in6 *) &(q->from); + return memcmp(&(expected->sin6_addr), &(received->sin6_addr), sizeof(struct in6_addr)); + } /* Unknown address family */ return 1; } @@ -1769,9 +1777,16 @@ tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) } FD_ZERO(&fds); + maxfd = 0; - FD_SET(dns_fds->v4fd, &fds); - maxfd = dns_fds->v4fd; + if (dns_fds->v4fd >= 0) { + FD_SET(dns_fds->v4fd, &fds); + maxfd = MAX(dns_fds->v4fd, maxfd); + } + if (dns_fds->v6fd >= 0) { + FD_SET(dns_fds->v6fd, &fds); + maxfd = MAX(dns_fds->v6fd, maxfd); + } if (bind_fd) { /* wait for replies from real DNS */ @@ -1815,6 +1830,9 @@ tunnel(int tun_fd, struct dnsfd *dns_fds, int bind_fd, int max_idle_time) if (FD_ISSET(dns_fds->v4fd, &fds)) { tunnel_dns(tun_fd, dns_fds->v4fd, dns_fds, bind_fd); } + if (FD_ISSET(dns_fds->v6fd, &fds)) { + tunnel_dns(tun_fd, dns_fds->v6fd, dns_fds, bind_fd); + } if (FD_ISSET(bind_fd, &fds)) { tunnel_bind(bind_fd, dns_fds); } @@ -2015,7 +2033,7 @@ static int read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) /* FIXME: dns_fds and tun_fd are because of raw_decode() below */ { - struct sockaddr_in from; + struct sockaddr_storage from; socklen_t addrlen; char packet[64*1024]; int r; @@ -2025,7 +2043,7 @@ read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) struct iovec iov; struct cmsghdr *cmsg; - addrlen = sizeof(struct sockaddr); + addrlen = sizeof(struct sockaddr_storage); iov.iov_base = packet; iov.iov_len = sizeof(packet); @@ -2039,7 +2057,7 @@ read_dns(int fd, struct dnsfd *dns_fds, int tun_fd, struct query *q) r = recvmsg(fd, &msg, 0); #else - addrlen = sizeof(struct sockaddr); + addrlen = sizeof(struct sockaddr_storage); r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); #endif /* !WINDOWS32 */ @@ -2236,26 +2254,27 @@ write_dns(int fd, struct query *q, char *data, int datalen, char downenc) } static void -usage() { +print_usage() { extern char *__progname; fprintf(stderr, "Usage: %s [-v] [-h] [-c] [-s] [-f] [-D] [-u user] " "[-t chrootdir] [-d device] [-m mtu] [-z context] " - "[-l ip address to listen on] [-p port] [-n external ip] " - "[-b dnsport] [-P password] [-F pidfile] [-i max idle time] " + "[-l ipv4 listen address] [-L ipv6 listen address] " + "[-p port] [-n external ip] [-b dnsport] " + "[-P password] [-F pidfile] [-i max idle time] " "tunnel_ip[/netmask] topdomain\n", __progname); +} + +static void +usage() { + print_usage(); exit(2); } static void help() { - extern char *__progname; - fprintf(stderr, "iodine IP over DNS tunneling server\n"); - fprintf(stderr, "Usage: %s [-v] [-h] [-c] [-s] [-f] [-D] [-u user] " - "[-t chrootdir] [-d device] [-m mtu] [-z context] " - "[-l ip address to listen on] [-p port] [-n external ip] [-b dnsport] [-P password] " - "[-F pidfile] tunnel_ip[/netmask] topdomain\n", __progname); + print_usage(); fprintf(stderr, " -v to print version info and exit\n"); fprintf(stderr, " -h to print this help and exit\n"); fprintf(stderr, " -c to disable check of client IP/port on each request\n"); @@ -2269,8 +2288,10 @@ help() { fprintf(stderr, " -d device to set tunnel device name\n"); fprintf(stderr, " -m mtu to set tunnel device mtu\n"); fprintf(stderr, " -z context to apply SELinux context after initialization\n"); - fprintf(stderr, " -l ip address to listen on for incoming dns traffic " + fprintf(stderr, " -l IPv4 address to listen on for incoming dns traffic " "(default 0.0.0.0)\n"); + fprintf(stderr, " -L IPv6 address to listen on for incoming dns traffic " + "(default ::)\n"); fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); fprintf(stderr, " -n ip to respond with to NS queries\n"); fprintf(stderr, " -b port to forward normal DNS queries to (on localhost)\n"); @@ -2295,6 +2316,7 @@ main(int argc, char **argv) { extern char *__progname; char *listen_ip4; + char *listen_ip6; char *errormsg; #ifndef WINDOWS32 struct passwd *pw; @@ -2322,7 +2344,9 @@ main(int argc, char **argv) int retval; int max_idle_time = 0; struct sockaddr_storage dns4addr; - int dns4addr_len; + socklen_t dns4addr_len; + struct sockaddr_storage dns6addr; + socklen_t dns6addr_len; #ifdef HAVE_SYSTEMD int nb_fds; #endif @@ -2341,6 +2365,7 @@ main(int argc, char **argv) mtu = 1130; /* Very many relays give fragsize 1150 or slightly higher for NULL; tun/zlib adds ~17 bytes. */ listen_ip4 = NULL; + listen_ip6 = NULL; port = 53; ns_ip = INADDR_ANY; ns_get_externalip = 0; @@ -2373,7 +2398,7 @@ main(int argc, char **argv) srand(time(NULL)); fw_query_init(); - while ((choice = getopt(argc, argv, "vcsfhDu:t:d:m:l:p:n:b:P:z:F:i:")) != -1) { + while ((choice = getopt(argc, argv, "vcsfhDu:t:d:m:l:L:p:n:b:P:z:F:i:")) != -1) { switch(choice) { case 'v': version(); @@ -2408,6 +2433,9 @@ main(int argc, char **argv) case 'l': listen_ip4 = optarg; break; + case 'L': + listen_ip6 = optarg; + break; case 'p': port = atoi(optarg); break; @@ -2505,7 +2533,12 @@ main(int argc, char **argv) dns4addr_len = get_addr(listen_ip4, port, AF_INET, AI_PASSIVE | AI_NUMERICHOST, &dns4addr); if (dns4addr_len < 0) { - warnx("Bad IP address to listen on."); + warnx("Bad IPv4 address to listen on."); + usage(); + } + dns6addr_len = get_addr(listen_ip6, port, AF_INET6, AI_PASSIVE | AI_NUMERICHOST, &dns6addr); + if (dns6addr_len < 0) { + warnx("Bad IPv6 address to listen on."); usage(); } @@ -2589,13 +2622,17 @@ main(int argc, char **argv) retval = 1; goto cleanup_tun; } + if ((dns_fds.v6fd = open_dns(&dns6addr, dns6addr_len)) < 0) { + retval = 1; + goto cleanup_dns; + } #ifdef HAVE_SYSTEMD } #endif if (bind_enable) { if ((bind_fd = open_dns_from_host(NULL, 0, AF_INET, 0)) < 0) { retval = 1; - goto cleanup_dns4; + goto cleanup_dns; } } @@ -2644,7 +2681,9 @@ main(int argc, char **argv) syslog(LOG_INFO, "stopping"); close_dns(bind_fd); -cleanup_dns4: +cleanup_dns: + if (dns_fds.v6fd >= 0) + close_dns(dns_fds.v6fd); if (dns_fds.v4fd >= 0) close_dns(dns_fds.v4fd); cleanup_tun: