diff options
author | David Sommerseth <dazo@users.sourceforge.net> | 2011-04-25 17:16:34 +0200 |
---|---|---|
committer | David Sommerseth <dazo@users.sourceforge.net> | 2011-04-25 17:18:40 +0200 |
commit | c5f7d08b8c3d4287dd40bbdf52525add8f5cee20 (patch) | |
tree | 0b5c57f614d395ff2033b45437abb48b84db1a8d | |
parent | dcf4bcc2d95faac5d7c4844ca841359526601c69 (diff) | |
parent | 15a436aac6b617b87bb234cdd7fedf1e603c470f (diff) | |
download | openvpn-c5f7d08b8c3d4287dd40bbdf52525add8f5cee20.tar.gz openvpn-c5f7d08b8c3d4287dd40bbdf52525add8f5cee20.tar.xz openvpn-c5f7d08b8c3d4287dd40bbdf52525add8f5cee20.zip |
Merge branch 'feat_ipv6_payload'
Conflicts:
options.c
- feat_ipv6_payload and feat_ip6_transport both updates
this file with presence information
Signed-off-by: David Sommerseth <dazo@users.sourceforge.net>
-rw-r--r-- | ChangeLog.IPv6 | 394 | ||||
-rw-r--r-- | README.IPv6 | 8 | ||||
-rw-r--r-- | TODO.IPv6 | 149 | ||||
-rw-r--r-- | forward.c | 3 | ||||
-rw-r--r-- | helper.c | 49 | ||||
-rw-r--r-- | init.c | 65 | ||||
-rw-r--r-- | init.h | 1 | ||||
-rw-r--r-- | misc.c | 4 | ||||
-rw-r--r-- | mroute.c | 139 | ||||
-rw-r--r-- | mroute.h | 4 | ||||
-rw-r--r-- | multi.c | 149 | ||||
-rw-r--r-- | openvpn.8 | 53 | ||||
-rw-r--r-- | openvpn.h | 8 | ||||
-rw-r--r-- | options.c | 269 | ||||
-rw-r--r-- | options.h | 21 | ||||
-rw-r--r-- | pool.c | 75 | ||||
-rw-r--r-- | pool.h | 7 | ||||
-rw-r--r-- | proto.h | 15 | ||||
-rw-r--r-- | push.c | 18 | ||||
-rw-r--r-- | route.c | 559 | ||||
-rw-r--r-- | route.h | 61 | ||||
-rw-r--r-- | socket.c | 119 | ||||
-rw-r--r-- | socket.h | 3 | ||||
-rw-r--r-- | tun.c | 554 | ||||
-rw-r--r-- | tun.h | 11 | ||||
-rw-r--r-- | win32.c | 16 | ||||
-rw-r--r-- | win32.h | 2 |
27 files changed, 2622 insertions, 134 deletions
diff --git a/ChangeLog.IPv6 b/ChangeLog.IPv6 new file mode 100644 index 0000000..38f4446 --- /dev/null +++ b/ChangeLog.IPv6 @@ -0,0 +1,394 @@ +Do 31. Dez 15:32:40 CET 2009 Gert Doering + + * Basic IPv6 p2mp functionality implemented + + * new options: + - server-ipv6 + - ifconfig-ipv6 + - ifconfig-ipv6-pool + - route-ipv6 + - iroute-ipv6 + + * modules touched: + - init.c: init & setup IPv6 route list & add/delete IPv6 routes + - tun.c: add "ifconfig" and "route" handling for IPv6 + - multi.c: IPv6 ifconfig-pool assignments + put to route-hash table + push to client + - pool.c: extend pools to handle IPv4+IPv6, and also return IPv6 address + IPv6 address saved to file if ifconfig-pool-persist is set + (but ignored on read due to the way pools work) + - mroute.c: handle reading src/dst addresses from IPv6 packets + (so multi.c can check against route-hash table) + handle printing of IPv6 mroute_addr structure + - helper.c: implement "server-ipv6" macro (->ifconfig-ipv6, pool, ...) + - options.c: implement all the new options + add helper functions for IPv6 address handling + - forward.c: tell do_route() about IPv6 routes + - route.c: handle IPv6 route lists + route option lists + extend add_routes() to do IPv4 + IPv6 route lists + extend delete_routes() to do IPv4 + IPv6 route lists + implement add_route_ipv6(), delete_route_ipv6() to call + system-dependend external program to do the work + - push.c: handle pushing of "ifconfig-ipv6" option + - socket.c: helper function to check & print IPv6 address strings + + * known issues: + - operating system support on all but Linux (ifconfig, route) + - route-ipv6 gateway handling + - iroute-ipv6 not implemented + - TAP support: ifconfig, routing (route needs gateway!) + + * release as patch 20091231-1 + +Thu Dec 31 17:02:08 CET 2009 + + * NetBSD port (NetBSD 3.1 on Sparc64) + + * mroute.c, socket.c: make byte/word access to in6_addr more portable + + * tun.c: fix IPv6 ifconfig arguments on NetBSD + + still doesn't work on NetBSD 3.1, "ifconfig tun0 inet6..." errors with + + ifconfig: SIOCAIFADDR: Address family not supported by protocol family + + (sys/net/if_tun.c, needs to be revision 1.80 or later, NetBSD PR 32944, + included in NetBSD 4.0 and up) + + +Fri Jan 1 14:07:15 CET 2010 + + * FreeBSD port (FreeBSD 6.3-p12 on i386) + + * tun.c: implement IPv6 ifconfig setting for FreeBSD + + * route.c: fix %s/%s argument to IPv6 route add/delete command for *BSD + + * TEST SUCCESS: FreeBSD 6.3-p12, server-ipv6, route-ipv6, ccd/iroute-ipv6 + + * multi.c: implement setting and deleting of iroute-ipv6 + (multi_add_iroutes(), multi_del_iroutes()) + * mroute.c: add mroute_helper_add_iroute6(), mroute_helper_del_iroute6() + * mroute.h: add prototypes, increase MR_HELPER_NET_LEN to 129 (/0.../128) + * multi.c: zeroize host part of IPv6 iroutes in multi_learn_in6_addr() + * mroute.c: implement mroute_addr_mask_host_bits() for IPv6 + + * TEST SUCCESS: Linux 2.6.30 (Gentoo)/iproute2, server-ipv6, ccd/iroute-ipv6 + + * TEST SUCCESS: Linux 2.6.30 (Gentoo)/ifconfig, client-ipv6 + + * TEST FAIL: NetBSD 5.0, IPv6 client + - "ifconfig tun0 .../64" does not create a "connected" route + - adding routes fails + + --> more work to do here. + + * release as patch 20100101-1 + + * TEST FAIL: + FreeBSD 6.3-p12 server "--topology subnet" + Linux/ifconfig client + - BSD sends ICMP6 neighbor solicitations, which are ignored by Linux + - server tun interface is not in p2p mode, client tun interface *is* + + * TEST SUCCESS: non-ipv6 enabled client -> "--server-ipv6" server + (warnings in the log file, but no malfunctions) + + +Sat Jan 2 19:48:35 CET 2010 + + * tun.c: change "ipv6_support()", do not turn off tt->ipv6 unconditionally + if we don't know about OS IPv6 support - just log warning + + * tun.c: implement "ifconfig inet6" setting for MacOS X / Darwin + + * route.c: split *BSD system dependent part of add/delete_route_ipv6() + into FreeBSD/Dragonfly and NetBSD/Darwin/OpenBSD variants + ("2001:db8::/64" vs. "2001:db8:: --prefixlen 64"). + + * tun.c: on MacOS X, NetBSD and OpenBSD, explicitely set on-link route + + * TEST SUCCESS: MacOS X, client-ipv6 with route-ipv6 + + +Sun Jan 3 10:55:31 CET 2010 + + * route.c: NetBSD fails with "-iface tun0", needs gateway address + (assume that the same syntax is needed for OpenBSD) + + * route.h: introduce "remote_endpoint_ipv6" into "struct route_ipv6_list" + + * init.c: pass "ifconfig_ipv6_remote" as gateway to init_route_ipv6_list() + + * route.c: + - init_route_ipv6(): use "remote_endpoint_ipv6" as IPv6 gateway address + if no gateway was specified explicitely + + - init_route_ipv6_list(): fill in "remote_endpoint_ipv6", if parseable + + - get rid of "GATEWAY-LESS ROUTE6" warning + + * route.c, add_route_ipv6() + - explicitely clear host bits of base address, to be able to more + easily set up "connected" /64 routes on NetBSD+Darwin + + - split system-dependent part between Darwin and NetBSD/OpenBSD + (Darwin can use "-iface tun0", NetBSD/OpenBSD get gateway address) + + - change Solaris comments from "known-broken" to "unknown" + + * tun.c: rework NetBSD tunnel initialization and tun_read() / tun_write() + to work the same way OpenBSD and NetBSD do - tunnel is put into + "multi-af" mode, and all packet read/write activity is prepended by + a 32 bit value specifying the address family. + + * TEST SUCCESS: NetBSD 5.0/Sparc64: client-ipv6 with route-ipv6 + + * TEST SUCCESS: MacOS X 10.5: client-ipv6 with route-ipv6 + + * (RE-)TEST SUCCESS: Linux/iproute2: server-ipv6 + Linux/ifconfig: client-ipv6 + FreeBSD 6.3: server-ipv6 + + * release as patch 20100103-1 + + * options.c: document all new options in "--help" + + * tun.c: fix typo in Solaris-specific section + + * socket.h, socket.c: change u_int32_t to uint32_t + (Solaris - and all the rest of the code uses "uintNN" anyway) + +Mon Jan 4 17:46:58 CET 2010 + + * socket.c: rework add_in6_addr() to use 32-bit access to struct in6_addr + (Solaris has no 16-bit values in union, but this is more elegant as well) + + * tun.c: fix "ifconfig inet6" command for Solaris + + * tun.c: make sure "tun0 inet6" is unplumbed first, cleanup leftovers + + * route.c: add routes with "metric 0" on solaris, otherwise they just + don't work (someone who understands Solaris might want to fix this). + + * Solaris "sort of" works now - ifconfig works, route add does not give + errors, "netstat -rn" looks right, but packets are discarded unless + the routes are installed with "metric 0". So we just use "metric 0"... + + * CAVEAT: Solaris "ifconfig ... preferred" interferes with source address + selection. So if there are any active IPv6 interfaces configured with + "preferred", packets leaving out the tunnel will use the wrong source + IPv6 address. Not fixable from within OpenVPN. + + * CAVEAT2: Solaris insists on doing DHCPv6 on tun0 interfaces by default, + so DHCPv6 solicitation packets will be seen. Since the server end has + no idea what to do with them, they are a harmless nuisance. Fixable + on the Solaris side via "ndpd.conf" (see ``man ifconfig''). + + * release as patch 20100104-1 + +Fri Jan 8 10:00:50 CET 2010 + + * import into git repository + + * options.c: add sanity checks for most typical error cases + (--ifconfig-ipv6-pool configured with no --ifconfig-ipv6, etc) + + * options.c: modify get_ipv6_addr() to be more flexible about netbits + (optional now, default to /64) and to return the address-without-netbits + string now (-> for options that want the IPv6 address in printable + form, but without /nn) + + * options.c: modify --ifconfig-ipv6 to optionally accept /netbits, + you can do now "ifconfig-ipv6 2001:df8::1/64 2001:df8::2" or just + "ifconfig-ipv6 2001:df8::5 2001:df8::7", defaulting to /64 + + * options.h: add necessary structure elements for --ifconfig-ipv6-push + + * options.c: implement "parse options" side of --ifconfig-ipv6-push + +Tue Jan 12 22:42:09 CET 2010 + + * tun.c: in TARGET_NETBSD #ifdef, distinguish between "old" code + (IPv4 only, but unmodified read/write) and "new" code (multi-af, + extra 32 bit AF on read/write of the tun interface) - pre-4.0 + NetBSD systems don't have TUNSIFHEAD, no way to have common code. + + * TEST SUCCESS: NetBSD 5.0/Sparc64: client-ipv6 with route-ipv6 (v4+v6) + + * TEST SUCCESS: NetBSD 3.1/Sparc64: client-ipv6 with route-ipv6 (v4-only) + +Thu Jan 14 15:41:50 CET 2010 + + * multi.c: if "--ifconfig-push" is used together with "--ifconfig-ipv6-pool" + and no "--ifconfig-ipv6-push" is seen, issue warning - the current + implementation of pools has IPv6 tied to IPv4, so if v4 does not use + the pool, it breaks for IPv6. Not a *big* problem (since there is + enough v6, just give those users a static v6 address as well), but needs + to be pointed out clearly. + + * release as patch 20100114-1 + +Tue Feb 16 14:43:28 CET 2010 + + * options.c: print "IPv6 payload patch" release date in "--version" + + * tun.c: undo change to init_tun() (moving "bool tun" and call to + "is_tun_p2p()" further up) - it wasn't needed and breaks "make check" + + * git stuff: rebase on David Sommerseth's openvpn-testing git tree + + * release as patch 20100216-1 + +Fri Feb 26 19:59:01 CET 2010 + + * init.c: initialize tuntap->ipv6 in do_init_tun() (to make sure it's + always initialized early-enough, independent of the sequence of + do_ifconfig()/open_tun() [see ifconfig_order() in tun.h]) + + * tun.c, init.c: remove "bool ipv6" argument to tuncfg(), open_tun() + and open_tun_generic() - obsoleted by previous change + + * tun.c: remove ipv6_support() - original purpose was unclear, and all + current platforms (except linux-very-old) fully support IPv6 now :-) + + * tun.c: initial implementation of "netsh" IPv6-ifconfig for Win32 + + * RE-TEST SUCCESS: Linux/i386/ifconfig, client-tun/net30, v4+v6 + +Sun Feb 28 17:05:57 CET 2010 + + * tun.c: NetBSD dependent part: correct destroying/re-creation of tun dev + + * tun.c: move adding of "connected" IPv6 prefix to new helper function, + add_route_connected_v6_net() + + * RE-TEST SUCCESS: NetBSD 5.0/Sparc64, client-tun/net30, v4+v6 + + * RE-TEST SUCCESS: NetBSD 3.1/Sparc64: client-tun/net30, v4-only + + * RE-TEST SUCCESS: Linux/i386/iproute2: server-tun/net30, v4+v6 + + * tun.c: add #ifdef TARGET_DARWIN block for *_tun() functions, to + be able to modify close_tun() for unconfiguring IPv6 + + * tun.c: on close_tun() on MacOS X, need to de-configure "lo0" route for + configured IPv6 address + + * RE-TEST SUCCESS: MacOS X (10.5)/i386: client-tun/net30, v4+v6 + + * route.c: implement ipv6 route adding / deletion via "netsh" for WIN32 + + * TEST FAIL: Windows XP fails, because the tun/tap driver does not + forward IPv6 frames kernel->userland if in "tun" mode + + * options.c: set IPv6 version to 20100228-1 + + * release as patch 20100228-1 + +Sun Mar 7 19:17:33 CET 2010 + + * options.c: set IPv6 version to 20100307-1 + + * TODO.IPv6: add note about OpenBSD TODO (#16) + + * route.c: set (and remove) "magic next hop" fe80::8 for IPv6 routes on + Win32 + + * install-win32/settings.in: bump TAP driver version from 9.6 to 9.7 + and TAP_RELDATE to "07/03/2010" + + * tap-win32/proto.h: add data types and definitions needed for IPv6 + + * tap-win32/types.h: add m_UserToTap_IPv6 ethernet header for IPv6 packets + + * tap-win32/tapdrvr.c: implement support for IPv6 in TUN mode: + - IPv6 packets User->OS need correct ether type + - IPv6 packets OS->User get correctly forwarded + - IPv6 neighbour discovery packets for "fe80::8" (magic address + installed as route-nexthop by OpenVPN.exe) get answered locally + + * TEST SUCCESS: WindowsXP/32bit: client-tun/net30, v4+v6 + + * tun.c: if IPv6 requested in TUN mode, and TUN/TAP driver version + is older than 9.7, log warning and disable IPv6 (won't work anyway). + + * release as patch 20100307-1 + +Sat Jul 10 14:37:52 CEST 2010 + + * TEST SUCCESS: point-to-point tun mode with --ifconfig-ipv6 between + Solaris10/sparc and Linux (Michal Ludvig) + (using the whiteboard tun driver on Solaris, otherwise "no IPv6") + +Sun Aug 8 12:30:44 CEST 2010 + + * route.c: split NetBSD and OpenBSD parts of add_route_ipv6() and + delete_route_ipv6(), implement OpenBSD variant + (needs "-prefixlen nn" while NetBSD uses "/nn") + + * tun.c: implement IPv6 ifconfig for OpenBSD + + * tun.c: destroy tunX interface at tun_close() on OpenBSD (cleanup) + + * TEST SUCCESS: OpenBSD 4.7: client-tun/net30, v4+v6 + +Thu Sep 2 21:18:32 CEST 2010 + + * tun.c: the TAP binary in 2.2-beta3 has the IPv6 related changes, but + the version number is 9.8 now -> check for 9.8, not 9.7 + +Wed Sep 22 22:20:37 CEST 2010 + + * tun.c: bugfix for Linux/iproute2/"topology subnet". Works :-) + + * TEST SUCCESS: Linux/ifconfig: client-tun/net30+subnet, v4+v6 + + * TEST SUCCESS: Linux/iproute2: client-tun/net30+subnet, v4+v6 + + * options.c: tag as 20100922-1 so "allmerged" users can see IPv6 change + +Fri Sep 24 17:57:41 CEST 2010 + + * TEST SUCCESS: Linux/<both>: client-tap, v4+v6, ping6 on connected addr + + * TEST FAIL: Linux/<both>: client-tap, v6, route6 (gateway missing) + +Do 21. Okt 19:36:49 CEST 2010 + + * t_client.sh.in: cherrypick commit f25fe91a40aa3f and 6f1e61b41be52 + (proper exit codes to signal "SKIP" if we do not want to run) + +So 16. Jan 17:25:23 CET 2011 + + * tun.c, route.c: cherrypick 121755c2cb4891f and f0eac1a5979096c67 + (TAP driver and "topology subnet" support for Solaris) + + * tun.c: add IPv6 configuration for TAP interfaces (<device>:1 inet6) + + * tun.c: on close_tun on Solaris, unplumb IPv6 TUN or TAP interfaces + + * TEST SUCCESS: OpenSolaris: client-tun, v4+v6 + TEST SUCCESS: OpenSolaris: client-tap, v4+v6, ping6 on connected addr + TEST FAIL: OpenSolaris: client-tap, v6, route6 (gateway missing) + +So 24. Apr 16:51:45 CEST 2011 + + * rebase to "beta2.2" branch (at 2.2RC2 tag) + + * mroute.c: remove mroute_helper_lock/_unlock() calls for IPv6 + * socket.c: remove locking with L_INET_NTOA mutex + (all the threading stuff got removed by David Sommerseth for 2.2) + + * mroute.c: remove duplicate mroute_helper_add_iroute6() and + mroute_helper_del_iroute6() - "git rebase" artefact + + * ChangeLog.IPv6 and TODO.IPv6: add to commit + + * options.c: tag as 20110424-2 (2.2RC2) + + * TEST SUCCESS: Linux/ifconfig: client-tun/net30+subnet, v4+v6 + + * TEST SUCCESS: Linux/iproute2: client-tun/net30+subnet, v4+v6 + diff --git a/README.IPv6 b/README.IPv6 new file mode 100644 index 0000000..ca578f2 --- /dev/null +++ b/README.IPv6 @@ -0,0 +1,8 @@ +This is an experimentally patched version of OpenVPN 2.1 with IPv6 +payload support. + +Go here for release notes and documentation: + + http://www.greenie.net/ipv6/openvpn.html + +Gert Doering, 31.12.2009 diff --git a/TODO.IPv6 b/TODO.IPv6 new file mode 100644 index 0000000..092a1a3 --- /dev/null +++ b/TODO.IPv6 @@ -0,0 +1,149 @@ +known issues for IPv6 payload support in OpenVPN +----------------------------------------------- + +1.) "--topology subnet" doesn't work together with IPv6 payload on FreeBSD + (verified for FreeBSD server, Linux/ifconfig client, problems + with ICMP6 neighbor solicitations from BSD not being answered by Linux) + +2.) NetBSD IPv6 support doesn't work + ("connected" route is not auto-created, "route-ipv6" adding fails) + + * fixed, 3.1.10 * + +3.) route deletion for IPv6 routes is not yet done + + * fixed for configured routes, 3.1.10 * + * missing for manual-ifconfig-connected (NetBSD, Darwin, Win32) + +4.) do "ifconfig tun0 inet6 unplumb" or "ifconfig tun0 destroy" for + Solaris, *BSD, ... at program termination time, to clean up leftovers + (unless tunnel persistance is desired). + + For Solaris, only the "ipv6 tun0" is affected, for the *BSDs all tun0 + stay around. + +4a.) deconfigure IPv6 on tun interface on session termination, otherwise + one could end up with something like this (on NetBSD): + +tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1500 + inet 10.9.0.18 -> 10.9.0.17 netmask 0xffffffff + inet6 fe80::a00:20ff:fece:d299%tun0 -> prefixlen 64 scopeid 0x3 + inet6 2001:608:4:eff::2000:3 -> prefixlen 64 + inet6 2001:608:4:eff::1:3 -> prefixlen 64 + + (pool was changed, previous address still active on tun0, breakage) + + * semi-fixed for NetBSD, 28.2.10, always do tun0 destroy / tun0 create + before actual ifconfig -- tunnel still lingers after OpenVPN quits + +4b.) verify this - on FreeBSD, tun0 is auto-destroyed if created by + opening /dev/tun (and lingers if created by "ifconfig tun0 create") + + -> use for persistant tunnels on not-linux? + +5.) add new option "ifconfig-ipv6-push" + (per-client static IPv6 assignment, -> radiusplugin, etc) + + * implemented, 14.1.10 * + +6.) add new option "route-ipv6-gateway" + +7.) add "full" gateway handling for IPv6 in route.c + (right now, the routes are just sent down the tun interface, if the + operating system in questions supports that, without care for the + gateway address - which does not work for gateways that are supposed + to point elsewhere. Also, it doesn't work for TAP interfaces. + +8.) full IPv6 support for TAP interfaces + (main issue should be routes+gateway - and testing :-) ) + + test 2010/09/24: TAP itself works on linux/ifconfig+iproute2, but + route-via-tap doesn't work at all (route points to "tap0" which fails) + +17:51:14.075412 fe:ab:6e:c5:53:71 > 33:33:ff:00:00:01, ethertype IPv6 (0x86dd), length 86: 2001:608:4:a053::1:0 > ff02::1:ff00:1: ICMP6, neighbor solicitation, who has 2001:608:4:a001::1, length 32 + + how is iroute-via-tap supposed to work?? + +9.) verify that iroute-ipv6 and route-ipv6 interact in the same way as + documented for iroute/route: + + A's subnet, OpenVPN must push this route to all clients + EXCEPT for A, since the subnet is already owned by A. + OpenVPN accomplishes this by not + not pushing a route to a client + if it matches one of the client's iroutes. + +10.) extend "ifconfig-ipv6" to handle specification of /netbits, pushing + of /netbits, and correctly ifconfig'ing this + (default, if not specified: /64) + +11.) do not add ipv6-routes if tun-ipv6 is not set - complain instead + + * done * 12.1.10 + +12.) handle incoming [::] and [fe80:...] packets in tun-p2mp MULTI mode + (most likely those are DAD packets) + silently ignore DAD? + Or accept-and-forward iff (multicast && client2client)? + handle NS/NA + +13.) from Martin List-Petersen: + + One thing, and I guess this requires modifications in + network-manager-openvpn: It also works, BUT ignores "push + route-ipv6-gateway" and "push route-ipv6 ...." (obviously routes pushed + from the server) entirely. + +14.) from ##openvpn-discussion: + + new features should be #ifdef'ed + + (check whether this is feasible at all) + +15.) IPv6 related environment variables + + - document all of them in openvpn.8 + - make sure that all existing IPv4 stuff has IPv6 counterparts + +16.) OpenBSD + - implement ifconfig/route for IPv6 + - revert ifconfig/open_tun order to "normal" (separate commit!!!) + (openvpn-devel, Subject: OpenBSD) + - test + +17.) client-option (Elwood) + - ignore-v6-push-options yes/no + - ignore-v6-route-push ("as for IPv4 routes") + +18.) fail-save? "what if 'ip -6 addr add' fails" -> fail, or fallback to v4? + (-> recomment setting "ignore-v6-push-options yes") + +19.) safety check: if connecting over IPv6 (v6 transport) and the pushed + route-ipv6 network encompasses the server IPv6 address, make sure + we at least log a warning (until we can fiddle with external routing + to make this work correctly). + +20.) show "route add" / "route delete" commands for IPv6 in log file + (we show the "ifconfig" commands, so why not the routes?) + + 2010-08-07: this is a null-feature - it's already there, but with + different debug level (M_INFO vs. D_ROUTE) so user + didn't notice + +21.) enable ipv6-only server operations + - decouple ipv6 pool handling from ipv4 pool + - make sure Rest of OpenVPN doesn't assume "there will always be IPv4" + +22.) implement --learn-address for IPv6 + +23.) FreeBSD 8 seems to require explicit setting of the "ifconfig" IPv6 + route, while FreeBSD 6+7 don't --> more testing, and code fix + + workaround for the time being: just add + + server-ipv6 2001:608:4:a051::/64 + route-ipv6 2001:608:4:a051::/64 + + to the config + + (problem + workaround applies both to tun and tap style devices) @@ -259,7 +259,8 @@ send_control_channel_string (struct context *c, const char *str, int msglevel) static void check_add_routes_action (struct context *c, const bool errors) { - do_route (&c->options, c->c1.route_list, c->c1.tuntap, c->plugins, c->c2.es); + do_route (&c->options, c->c1.route_list, c->c1.route_ipv6_list, + c->c1.tuntap, c->plugins, c->c2.es); update_time (); event_timeout_clear (&c->c2.route_wakeup); event_timeout_clear (&c->c2.route_wakeup_expire); @@ -142,6 +142,55 @@ helper_client_server (struct options *o) #if P2MP #if P2MP_SERVER + + /* + * + * HELPER DIRECTIVE for IPv6 + * + * server-ipv6 2001:db8::/64 + * + * EXPANDS TO: + * + * tun-ipv6 + * push "tun-ipv6" + * ifconfig-ipv6 2001:db8::1 2001:db8::2 + * if !nopool: + * ifconfig-ipv6-pool 2001:db8::1:0/64 + * + */ + if ( o->server_ipv6_defined ) + { + if ( ! o->server_defined ) + { + msg (M_USAGE, "--server-ipv6 must be used together with --server"); + } + if ( o->server_flags & SF_NOPOOL ) + { + msg( M_USAGE, "--server-ipv6 is incompatible with 'nopool' option" ); + } + if ( o->ifconfig_ipv6_pool_defined ) + { + msg( M_USAGE, "--server-ipv6 already defines an ifconfig-ipv6-pool, so you can't also specify --ifconfig-pool explicitly"); + } + + /* local ifconfig is "base address + 1" and "+2" */ + o->ifconfig_ipv6_local = + print_in6_addr( add_in6_addr( o->server_network_ipv6, 1), 0, &o->gc ); + o->ifconfig_ipv6_remote = + print_in6_addr( add_in6_addr( o->server_network_ipv6, 2), 0, &o->gc ); + + /* pool starts at "base address + 0x10000" */ + ASSERT( o->server_netbits_ipv6 < 96 ); /* want 32 bits */ + o->ifconfig_ipv6_pool_defined = true; + o->ifconfig_ipv6_pool_base = + add_in6_addr( o->server_network_ipv6, 0x10000 ); + o->ifconfig_ipv6_pool_netbits = o->server_netbits_ipv6; + + o->tun_ipv6 = true; + + push_option( o, "tun-ipv6", M_USAGE ); + } + /* * * HELPER DIRECTIVE: @@ -843,7 +843,7 @@ do_persist_tuntap (const struct options *options) msg (M_FATAL|M_OPTERR, "options --mktun or --rmtun should only be used together with --dev"); tuncfg (options->dev, options->dev_type, options->dev_node, - options->tun_ipv6, options->persist_mode, + options->persist_mode, options->username, options->groupname, &options->tuntap_options); if (options->persist_mode && options->lladdr) set_lladdr(options->dev, options->lladdr, NULL); @@ -1066,6 +1066,8 @@ do_alloc_route_list (struct context *c) { if (c->options.routes && !c->c1.route_list) c->c1.route_list = new_route_list (c->options.max_routes, &c->gc); + if (c->options.routes_ipv6 && !c->c1.route_ipv6_list) + c->c1.route_ipv6_list = new_route_ipv6_list (c->options.max_routes, &c->gc); } @@ -1108,6 +1110,45 @@ do_init_route_list (const struct options *options, } } +static void +do_init_route_ipv6_list (const struct options *options, + struct route_ipv6_list *route_ipv6_list, + bool fatal, + struct env_set *es) +{ + const char *gw = NULL; + int dev = dev_type_enum (options->dev, options->dev_type); + int metric = 0; + + if (dev != DEV_TYPE_TUN ) + msg( M_WARN, "IPv6 routes on TAP devices are going to fail on some platforms (need gateway spec)" ); /* TODO-GERT */ + + gw = options->ifconfig_ipv6_remote; /* default GW = remote end */ +#if 0 /* not yet done for IPv6 - TODO!*/ + if ( options->route_ipv6_default_gateway ) /* override? */ + gw = options->route_ipv6_default_gateway; +#endif + + if (options->route_default_metric) + metric = options->route_default_metric; + + if (!init_route_ipv6_list (route_ipv6_list, + options->routes_ipv6, + gw, + metric, + es)) + { + if (fatal) + openvpn_exit (OPENVPN_EXIT_STATUS_ERROR); /* exit point */ + } + else + { + /* copy routes to environment */ + setenv_routes_ipv6 (es, route_ipv6_list); + } +} + + /* * Called after all initialization has been completed. */ @@ -1176,12 +1217,13 @@ initialization_sequence_completed (struct context *c, const unsigned int flags) void do_route (const struct options *options, struct route_list *route_list, + struct route_ipv6_list *route_ipv6_list, const struct tuntap *tt, const struct plugin_list *plugins, struct env_set *es) { - if (!options->route_noexec && route_list) - add_routes (route_list, tt, ROUTE_OPTION_FLAGS (options), es); + if (!options->route_noexec && ( route_list || route_ipv6_list ) ) + add_routes (route_list, route_ipv6_list, tt, ROUTE_OPTION_FLAGS (options), es); if (plugin_defined (plugins, OPENVPN_PLUGIN_ROUTE_UP)) { @@ -1238,11 +1280,16 @@ do_init_tun (struct context *c) c->options.topology, c->options.ifconfig_local, c->options.ifconfig_remote_netmask, + c->options.ifconfig_ipv6_local, + c->options.ifconfig_ipv6_remote, addr_host (&c->c1.link_socket_addr.local), addr_host (&c->c1.link_socket_addr.remote), !c->options.ifconfig_nowarn, c->c2.es); + /* flag tunnel for IPv6 config if --tun-ipv6 is set */ + c->c1.tuntap->ipv6 = c->options.tun_ipv6; + init_tun_post (c->c1.tuntap, &c->c2.frame, &c->options.tuntap_options); @@ -1274,6 +1321,8 @@ do_open_tun (struct context *c) /* parse and resolve the route option list */ if (c->options.routes && c->c1.route_list && c->c2.link_socket) do_init_route_list (&c->options, c->c1.route_list, &c->c2.link_socket->info, false, c->c2.es); + if (c->options.routes_ipv6 && c->c1.route_ipv6_list ) + do_init_route_ipv6_list (&c->options, c->c1.route_ipv6_list, false, c->c2.es); /* do ifconfig */ if (!c->options.ifconfig_noexec @@ -1290,7 +1339,7 @@ do_open_tun (struct context *c) /* open the tun device */ open_tun (c->options.dev, c->options.dev_type, c->options.dev_node, - c->options.tun_ipv6, c->c1.tuntap); + c->c1.tuntap); /* set the hardware address */ if (c->options.lladdr) @@ -1320,7 +1369,8 @@ do_open_tun (struct context *c) /* possibly add routes */ if (!c->options.route_delay_defined) - do_route (&c->options, c->c1.route_list, c->c1.tuntap, c->plugins, c->c2.es); + do_route (&c->options, c->c1.route_list, c->c1.route_ipv6_list, + c->c1.tuntap, c->plugins, c->c2.es); /* * Did tun/tap driver give us an MTU? @@ -1395,8 +1445,9 @@ do_close_tun (struct context *c, bool force) #endif /* delete any routes we added */ - if (c->c1.route_list) - delete_routes (c->c1.route_list, c->c1.tuntap, ROUTE_OPTION_FLAGS (&c->options), c->c2.es); + if (c->c1.route_list || c->c1.route_ipv6_list ) + delete_routes (c->c1.route_list, c->c1.route_ipv6_list, + c->c1.tuntap, ROUTE_OPTION_FLAGS (&c->options), c->c2.es); /* actually close tun/tap device based on --down-pre flag */ if (!c->options.down_pre) @@ -63,6 +63,7 @@ void init_instance (struct context *c, const struct env_set *env, const unsigned void do_route (const struct options *options, struct route_list *route_list, + struct route_ipv6_list *route_ipv6_list, const struct tuntap *tt, const struct plugin_list *plugins, struct env_set *es); @@ -1004,7 +1004,9 @@ setenv_str_ex (struct env_set *es, { const char *str = construct_name_value (name_tmp, val_tmp, &gc); env_set_add (es, str); - /*msg (M_INFO, "SETENV_ES '%s'", str);*/ +#if DEBUG_VERBOSE_SETENV + msg (M_INFO, "SETENV_ES '%s'", str); +#endif } else env_set_del (es, name_tmp); @@ -88,12 +88,33 @@ mroute_get_in_addr_t (struct mroute_addr *ma, const in_addr_t src, unsigned int } } +static inline void +mroute_get_in6_addr (struct mroute_addr *ma, const struct in6_addr src, unsigned int mask) +{ + if (ma) + { + ma->type = MR_ADDR_IPV6 | mask; + ma->netbits = 0; + ma->len = 16; + *(struct in6_addr *)ma->addr = src; + } +} + static inline bool mroute_is_mcast (const in_addr_t addr) { return ((addr & htonl(IP_MCAST_SUBNET_MASK)) == htonl(IP_MCAST_NETWORK)); } +/* RFC 4291, 2.7, "binary 11111111 at the start of an address identifies + * the address as being a multicast address" + */ +static inline bool +mroute_is_mcast_ipv6 (const struct in6_addr addr) +{ + return (addr.s6_addr[0] == 0xff); +} + #ifdef ENABLE_PF static unsigned int @@ -155,10 +176,29 @@ mroute_extract_addr_ipv4 (struct mroute_addr *src, } break; case 6: - { - msg (M_WARN, "Need IPv6 code in mroute_extract_addr_from_packet"); - break; - } + if (BLEN (buf) >= (int) sizeof (struct openvpn_ipv6hdr)) + { + const struct openvpn_ipv6hdr *ipv6 = (const struct openvpn_ipv6hdr *) BPTR (buf); +#if 0 /* very basic debug */ + struct gc_arena gc = gc_new (); + msg( M_INFO, "IPv6 packet! src=%s, dst=%s", + print_in6_addr( ipv6->saddr, 0, &gc ), + print_in6_addr( ipv6->daddr, 0, &gc )); + gc_free (&gc); +#endif + + mroute_get_in6_addr (src, ipv6->saddr, 0); + mroute_get_in6_addr (dest, ipv6->daddr, 0); + + if (mroute_is_mcast_ipv6 (ipv6->daddr)) + ret |= MROUTE_EXTRACT_MCAST; + + ret |= MROUTE_EXTRACT_SUCCEEDED; + } + break; + default: + msg (M_WARN, "IP packet with unknown IP version=%d seen", + OPENVPN_IPH_GET_VER (*BPTR(buf))); } } return ret; @@ -274,14 +314,36 @@ bool mroute_extract_openvpn_sockaddr (struct mroute_addr *addr, * Zero off the host bits in an address, leaving * only the network bits, using the netbits member of * struct mroute_addr as the controlling parameter. + * + * TODO: this is called for route-lookup for every yet-unhashed + * destination address, so for lots of active net-iroutes, this + * might benefit from some "zeroize 32 bit at a time" improvements */ void mroute_addr_mask_host_bits (struct mroute_addr *ma) { in_addr_t addr = ntohl(*(in_addr_t*)ma->addr); - ASSERT ((ma->type & MR_ADDR_MASK) == MR_ADDR_IPV4); - addr &= netbits_to_netmask (ma->netbits); - *(in_addr_t*)ma->addr = htonl (addr); + if ((ma->type & MR_ADDR_MASK) == MR_ADDR_IPV4) + { + addr &= netbits_to_netmask (ma->netbits); + *(in_addr_t*)ma->addr = htonl (addr); + } + else if ((ma->type & MR_ADDR_MASK) == MR_ADDR_IPV6) + { + int byte = ma->len-1; /* rightmost byte in address */ + int bits_to_clear = 128 - ma->netbits; + + while( byte >= 0 && bits_to_clear > 0 ) + { + if ( bits_to_clear >= 8 ) + { ma->addr[byte--] = 0; bits_to_clear -= 8; } + else + { ma->addr[byte--] &= (~0 << bits_to_clear); bits_to_clear = 0; } + } + ASSERT( bits_to_clear == 0 ); + } + else + ASSERT(0); } /* @@ -359,17 +421,24 @@ mroute_addr_print_ex (const struct mroute_addr *ma, } break; case MR_ADDR_IPV6: - buf_printf (&out, "IPV6"); - break; - default: - buf_printf (&out, "UNKNOWN"); - break; - } - return BSTR (&out); - } - else - return "[NULL]"; -} + { + buf_printf (&out, "%s", + print_in6_addr( *(struct in6_addr*)&maddr.addr, 0, gc)); + if (maddr.type & MR_WITH_NETBITS) + { + buf_printf (&out, "/%d", maddr.netbits); + } + } + break; + default: + buf_printf (&out, "UNKNOWN"); + break; + } + return BSTR (&out); + } + else + return "[NULL]"; + } /* * mroute_helper's main job is keeping track of @@ -440,6 +509,40 @@ mroute_helper_del_iroute (struct mroute_helper *mh, const struct iroute *ir) } } +/* this is a bit inelegant, we really should have a helper to that + * is only passed the netbits value, and not the whole struct iroute * + * - thus one helper could do IPv4 and IPv6. For the sake of "not change + * code unrelated to IPv4" this is left for later cleanup, for now. + */ +void +mroute_helper_add_iroute6 (struct mroute_helper *mh, + const struct iroute_ipv6 *ir6) +{ + if (ir6->netbits >= 0) + { + ASSERT (ir6->netbits < MR_HELPER_NET_LEN); + ++mh->cache_generation; + ++mh->net_len_refcount[ir6->netbits]; + if (mh->net_len_refcount[ir6->netbits] == 1) + mroute_helper_regenerate (mh); + } +} + +void +mroute_helper_del_iroute6 (struct mroute_helper *mh, + const struct iroute_ipv6 *ir6) +{ + if (ir6->netbits >= 0) + { + ASSERT (ir6->netbits < MR_HELPER_NET_LEN); + ++mh->cache_generation; + --mh->net_len_refcount[ir6->netbits]; + ASSERT (mh->net_len_refcount[ir6->netbits] >= 0); + if (!mh->net_len_refcount[ir6->netbits]) + mroute_helper_regenerate (mh); + } +} + void mroute_helper_free (struct mroute_helper *mh) { @@ -85,7 +85,7 @@ struct mroute_addr { /* * Number of bits in an address. Should be raised for IPv6. */ -#define MR_HELPER_NET_LEN 32 +#define MR_HELPER_NET_LEN 129 /* * Used to help maintain CIDR routing table. @@ -127,6 +127,8 @@ struct mroute_helper *mroute_helper_init (int ageable_ttl_secs); void mroute_helper_free (struct mroute_helper *mh); void mroute_helper_add_iroute (struct mroute_helper *mh, const struct iroute *ir); void mroute_helper_del_iroute (struct mroute_helper *mh, const struct iroute *ir); +void mroute_helper_add_iroute6 (struct mroute_helper *mh, const struct iroute_ipv6 *ir6); +void mroute_helper_del_iroute6 (struct mroute_helper *mh, const struct iroute_ipv6 *ir6); /* * Given a raw packet in buf, return the src and dest @@ -316,25 +316,18 @@ multi_init (struct multi_context *m, struct context *t, bool tcp_mode, int threa */ if (t->options.ifconfig_pool_defined) { - if (dev == DEV_TYPE_TAP) - { - m->ifconfig_pool = ifconfig_pool_init (IFCONFIG_POOL_INDIV, - t->options.ifconfig_pool_start, - t->options.ifconfig_pool_end, - t->options.duplicate_cn); - } - else if (dev == DEV_TYPE_TUN) - { - m->ifconfig_pool = ifconfig_pool_init ( - (t->options.topology == TOP_NET30) ? IFCONFIG_POOL_30NET : IFCONFIG_POOL_INDIV, - t->options.ifconfig_pool_start, - t->options.ifconfig_pool_end, - t->options.duplicate_cn); - } - else - { - ASSERT (0); - } + int pool_type = IFCONFIG_POOL_INDIV; + + if ( dev == DEV_TYPE_TUN && t->options.topology == TOP_NET30 ) + pool_type = IFCONFIG_POOL_30NET; + + m->ifconfig_pool = ifconfig_pool_init (pool_type, + t->options.ifconfig_pool_start, + t->options.ifconfig_pool_end, + t->options.duplicate_cn, + t->options.ifconfig_ipv6_pool_defined, + t->options.ifconfig_ipv6_pool_base, + t->options.ifconfig_ipv6_pool_netbits ); /* reload pool data from file */ if (t->c1.ifconfig_pool_persist) @@ -429,10 +422,14 @@ multi_del_iroutes (struct multi_context *m, struct multi_instance *mi) { const struct iroute *ir; + const struct iroute_ipv6 *ir6; if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN) { for (ir = mi->context.options.iroutes; ir != NULL; ir = ir->next) mroute_helper_del_iroute (m->route_helper, ir); + + for ( ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next ) + mroute_helper_del_iroute6 (m->route_helper, ir6); } } @@ -1078,6 +1075,37 @@ multi_learn_in_addr_t (struct multi_context *m, } } +static struct multi_instance * +multi_learn_in6_addr (struct multi_context *m, + struct multi_instance *mi, + struct in6_addr a6, + int netbits, /* -1 if host route, otherwise # of network bits in address */ + bool primary) +{ + struct mroute_addr addr; + + addr.len = 16; + addr.type = MR_ADDR_IPV6; + addr.netbits = 0; + memcpy( &addr.addr, &a6, sizeof(a6) ); + + if (netbits >= 0) + { + addr.type |= MR_WITH_NETBITS; + addr.netbits = (uint8_t) netbits; + mroute_addr_mask_host_bits( &addr ); + } + + { + struct multi_instance *owner = multi_learn_addr (m, mi, &addr, 0); +#ifdef MANAGEMENT_DEF_AUTH + if (management && owner) + management_learn_addr (management, &mi->context.c2.mda_context, &addr, primary); +#endif + return owner; + } +} + /* * A new client has connected, add routes (server -> client) * to internal routing table. @@ -1088,6 +1116,7 @@ multi_add_iroutes (struct multi_context *m, { struct gc_arena gc = gc_new (); const struct iroute *ir; + const struct iroute_ipv6 *ir6; if (TUNNEL_TYPE (mi->context.c1.tuntap) == DEV_TYPE_TUN) { mi->did_iroutes = true; @@ -1107,6 +1136,22 @@ multi_add_iroutes (struct multi_context *m, multi_learn_in_addr_t (m, mi, ir->network, ir->netbits, false); } + for ( ir6 = mi->context.options.iroutes_ipv6; ir6 != NULL; ir6 = ir6->next ) + { + if (ir6->netbits >= 0) + msg (D_MULTI_LOW, "MULTI: internal route %s/%d -> %s", + print_in6_addr (ir6->network, 0, &gc), + ir6->netbits, + multi_instance_string (mi, false, &gc)); + else + msg (D_MULTI_LOW, "MULTI: internal route %s -> %s", + print_in6_addr (ir6->network, 0, &gc), + multi_instance_string (mi, false, &gc)); + + mroute_helper_add_iroute6 (m->route_helper, ir6); + + multi_learn_in6_addr (m, mi, ir6->network, ir6->netbits, false); + } } gc_free (&gc); } @@ -1192,21 +1237,37 @@ multi_select_virtual_addr (struct multi_context *m, struct multi_instance *mi) mi->context.c2.push_ifconfig_defined = true; mi->context.c2.push_ifconfig_local = mi->context.options.push_ifconfig_local; mi->context.c2.push_ifconfig_remote_netmask = mi->context.options.push_ifconfig_remote_netmask; + + /* the current implementation does not allow "static IPv4, pool IPv6", + * (see below) so issue a warning if that happens - don't break the + * session, though, as we don't even know if this client WANTS IPv6 + */ + if ( mi->context.c1.tuntap->ipv6 && + mi->context.options.ifconfig_ipv6_pool_defined && + ! mi->context.options.push_ifconfig_ipv6_defined ) + { + msg( M_INFO, "MULTI_sva: WARNING: if --ifconfig-push is used for IPv4, automatic IPv6 assignment from --ifconfig-ipv6-pool does not work. Use --ifconfig-ipv6-push for IPv6 then." ); + } } else if (m->ifconfig_pool && mi->vaddr_handle < 0) /* otherwise, choose a pool address */ { in_addr_t local=0, remote=0; + struct in6_addr remote_ipv6; const char *cn = NULL; if (!mi->context.options.duplicate_cn) cn = tls_common_name (mi->context.c2.tls_multi, true); - mi->vaddr_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, cn); + mi->vaddr_handle = ifconfig_pool_acquire (m->ifconfig_pool, &local, &remote, &remote_ipv6, cn); if (mi->vaddr_handle >= 0) { const int tunnel_type = TUNNEL_TYPE (mi->context.c1.tuntap); const int tunnel_topology = TUNNEL_TOPOLOGY (mi->context.c1.tuntap); + msg( M_INFO, "MULTI_sva: pool returned IPv4=%s, IPv6=%s", + print_in_addr_t( remote, 0, &gc ), + print_in6_addr( remote_ipv6, 0, &gc ) ); + /* set push_ifconfig_remote_netmask from pool ifconfig address(es) */ mi->context.c2.push_ifconfig_local = remote; if (tunnel_type == DEV_TYPE_TAP || (tunnel_type == DEV_TYPE_TUN && tunnel_topology == TOP_SUBNET)) @@ -1228,12 +1289,46 @@ multi_select_virtual_addr (struct multi_context *m, struct multi_instance *mi) else msg (D_MULTI_ERRORS, "MULTI: no --ifconfig-pool netmask parameter is available to push to %s", multi_instance_string (mi, false, &gc)); + + if ( mi->context.options.ifconfig_ipv6_pool_defined ) + { + mi->context.c2.push_ifconfig_ipv6_local = remote_ipv6; + mi->context.c2.push_ifconfig_ipv6_remote = + mi->context.c1.tuntap->local_ipv6; + mi->context.c2.push_ifconfig_ipv6_netbits = + mi->context.options.ifconfig_ipv6_pool_netbits; + mi->context.c2.push_ifconfig_ipv6_defined = true; + } } else { msg (D_MULTI_ERRORS, "MULTI: no free --ifconfig-pool addresses are available"); } } + + /* IPv6 push_ifconfig is a bit problematic - since IPv6 shares the + * pool handling with IPv4, the combination "static IPv4, dynamic IPv6" + * will fail (because no pool will be allocated in this case). + * OTOH, this doesn't make too much sense in reality - and the other + * way round ("dynamic IPv4, static IPv6") or "both static" makes sense + * -> and so it's implemented right now + */ + if ( mi->context.c1.tuntap->ipv6 && + mi->context.options.push_ifconfig_ipv6_defined ) + { + mi->context.c2.push_ifconfig_ipv6_local = + mi->context.options.push_ifconfig_ipv6_local; + mi->context.c2.push_ifconfig_ipv6_remote = + mi->context.options.push_ifconfig_ipv6_remote; + mi->context.c2.push_ifconfig_ipv6_netbits = + mi->context.options.push_ifconfig_ipv6_netbits; + mi->context.c2.push_ifconfig_ipv6_defined = true; + + msg( M_INFO, "MULTI_sva: push_ifconfig_ipv6 %s/%d", + print_in6_addr( mi->context.c2.push_ifconfig_ipv6_local, 0, &gc ), + mi->context.c2.push_ifconfig_ipv6_netbits ); + } + gc_free (&gc); } @@ -1272,6 +1367,11 @@ multi_set_virtual_addr_env (struct multi_context *m, struct multi_instance *mi) SA_SET_IF_NONZERO); } } + + /* TODO: I'm not exactly sure what these environment variables are + * used for, but if we have them for IPv4, we should also have + * them for IPv6, no? + */ } /* @@ -1661,6 +1761,15 @@ multi_connection_established (struct multi_context *m, struct multi_instance *mi print_in_addr_t (mi->context.c2.push_ifconfig_local, 0, &gc)); } + if (mi->context.c2.push_ifconfig_ipv6_defined) + { + multi_learn_in6_addr (m, mi, mi->context.c2.push_ifconfig_ipv6_local, -1, true); + /* TODO: find out where addresses are "unlearned"!! */ + msg (D_MULTI_LOW, "MULTI: primary virtual IPv6 for %s: %s", + multi_instance_string (mi, false, &gc), + print_in6_addr (mi->context.c2.push_ifconfig_ipv6_local, 0, &gc)); + } + /* add routes locally, pointing to new client, if --iroute options have been specified */ multi_add_iroutes (m, mi); @@ -789,6 +789,8 @@ or .B \-\-dev tunX. A warning will be displayed if no specific IPv6 TUN support for your OS has been compiled into OpenVPN. + +See below for further IPv6-related configuration options. .\"********************************************************* .TP .B \-\-dev-node node @@ -4936,6 +4938,57 @@ if certificates are stored as private objects. .B \-\-verb option can be used BEFORE this option to produce debugging information. .\"********************************************************* +.SS IPv6 Related Options +.\"********************************************************* +The following options exist to support IPv6 tunneling in peer-to-peer +and client-server mode. As of now, this is just very basic +documentation of the IPv6-related options. More documentation can be +found on http://www.greenie.net/ipv6/openvpn.html. +.TP +.B --ifconfig-ipv6 ipv6addr/bits ipv6remote +configure IPv6 address +.B ipv6addr/bits +on the ``tun'' device. The second parameter is used as route target for +.B --route-ipv6 +if no gateway is specified. +.TP +.B --route-ipv6 ipv6addr/bits [gateway] [metric] +setup IPv6 routing in the system to send the specified IPv6 network +into OpenVPN's ``tun'' device +.TP +.B --server-ipv6 ipv6addr/bits +convenience-function to enable a number of IPv6 related options at +once, namely +.B --ifconfig-ipv6, --ifconfig-ipv6-pool, --tun-ipv6 +and +.B --push tun-ipv6 +Is only accepted if ``--mode server'' or ``--server'' is set. +.TP +.B --ifconfig-ipv6-pool ipv6addr/bits +Specify an IPv6 address pool for dynamic assignment to clients. The +pool starts at +.B ipv6addr +and increments by +1 for every new client (linear mode). The +.B /bits +setting controls the size of the pool. +.TP +.B --ifconfig-ipv6-push ipv6addr/bits ipv6remote +for ccd/ per-client static IPv6 interface configuration, see +.B --client-config-dir +and +.B --ifconfig-push +for more details. +.TP +.B --iroute-ipv6 ipv6addr/bits +for ccd/ per-client static IPv6 route configuration, see +.B --iroute +for more details how to setup and use this, and how +.B --iroute +and +.B --route +interact. + +.\"********************************************************* .SH SCRIPTING AND ENVIRONMENTAL VARIABLES OpenVPN exports a series of environmental variables for use by user-defined scripts. @@ -165,6 +165,9 @@ struct context_1 /* list of --route directives */ struct route_list *route_list; + /* list of --route-ipv6 directives */ + struct route_ipv6_list *route_ipv6_list; + /* --status file */ struct status_output *status_output; bool status_output_owned; @@ -417,6 +420,11 @@ struct context_2 in_addr_t push_ifconfig_local; in_addr_t push_ifconfig_remote_netmask; + bool push_ifconfig_ipv6_defined; + struct in6_addr push_ifconfig_ipv6_local; + int push_ifconfig_ipv6_netbits; + struct in6_addr push_ifconfig_ipv6_remote; + /* client authentication state, CAS_SUCCEEDED must be 0 */ # define CAS_SUCCEEDED 0 # define CAS_PENDING 1 @@ -86,6 +86,7 @@ const char title_string[] = #ifdef USE_PF_INET6 " [PF_INET6]" #endif + " [IPv6 payload 20110424-2 (2.2RC2)]" " built on " __DATE__ ; @@ -181,6 +182,8 @@ static const char usage_message[] = " addresses outside of the subnets used by either peer.\n" " TAP: configure device to use IP address l as a local\n" " endpoint and rn as a subnet mask.\n" + "--ifconfig-ipv6 l r : configure device to use IPv6 address l as local\n" + " endpoint (as a /64) and r as remote endpoint\n" "--ifconfig-noexec : Don't actually execute ifconfig/netsh command, instead\n" " pass --ifconfig parms by environment to scripts.\n" "--ifconfig-nowarn : Don't warn if the --ifconfig option on this side of the\n" @@ -191,6 +194,10 @@ static const char usage_message[] = " netmask default: 255.255.255.255\n" " gateway default: taken from --route-gateway or --ifconfig\n" " Specify default by leaving blank or setting to \"nil\".\n" + "--route-ipv6 network/bits [gateway] [metric] :\n" + " Add IPv6 route to routing table after connection\n" + " is established. Multiple routes can be specified.\n" + " gateway default: taken from --route-ipv6-gateway or --ifconfig\n" "--max-routes n : Specify the maximum number of routes that may be defined\n" " or pulled from a server.\n" "--route-gateway gw|'dhcp' : Specify a default gateway for use with --route.\n" @@ -379,6 +386,7 @@ static const char usage_message[] = "\n" "Multi-Client Server options (when --mode server is used):\n" "--server network netmask : Helper option to easily configure server mode.\n" + "--server-ipv6 network/bits : Configure IPv6 server mode.\n" "--server-bridge [IP netmask pool-start-IP pool-end-IP] : Helper option to\n" " easily configure ethernet bridging server mode.\n" "--push \"option\" : Push a config file option back to the peer for remote\n" @@ -392,10 +400,16 @@ static const char usage_message[] = "--ifconfig-pool-persist file [seconds] : Persist/unpersist ifconfig-pool\n" " data to file, at seconds intervals (default=600).\n" " If seconds=0, file will be treated as read-only.\n" + "--ifconfig-ipv6-pool base-IP/bits : set aside an IPv6 network block\n" + " to be dynamically allocated to connecting clients.\n" "--ifconfig-push local remote-netmask : Push an ifconfig option to remote,\n" " overrides --ifconfig-pool dynamic allocation.\n" " Only valid in a client-specific config file.\n" + "--ifconfig-ipv6-push local/bits remote : Push an ifconfig-ipv6 option to\n" + " remote, overrides --ifconfig-ipv6-pool allocation.\n" + " Only valid in a client-specific config file.\n" "--iroute network [netmask] : Route subnet to client.\n" + "--iroute-ipv6 network/bits : Route IPv6 subnet to client.\n" " Sets up internal routes only.\n" " Only valid in a client-specific config file.\n" "--disable : Client is disabled.\n" @@ -880,6 +894,78 @@ get_ip_addr (const char *ip_string, int msglevel, bool *error) return ret; } +/* helper: parse a text string containing an IPv6 address + netbits + * in "standard format" (2001:dba::/32) + * "/nn" is optional, default to /64 if missing + * + * return true if parsing succeeded, modify *network and *netbits + * return address part without "/nn" in *printable_ipv6 (if != NULL) + */ +bool +get_ipv6_addr( const char * prefix_str, struct in6_addr *network, + unsigned int * netbits, char ** printable_ipv6, int msglevel ) +{ + int rc; + char * sep, * endp; + int bits; + struct in6_addr t_network; + + sep = strchr( prefix_str, '/' ); + if ( sep == NULL ) + { + bits = 64; + } + else + { + bits = strtol( sep+1, &endp, 10 ); + if ( *endp != '\0' || bits < 0 || bits > 128 ) + { + msg (msglevel, "IPv6 prefix '%s': invalid '/bits' spec", prefix_str); + return false; + } + } + + /* temporary replace '/' in caller-provided string with '\0', otherwise + * inet_pton() will refuse prefix string + * (alternative would be to strncpy() the prefix to temporary buffer) + */ + + if ( sep != NULL ) *sep = '\0'; + + rc = inet_pton( AF_INET6, prefix_str, &t_network ); + + if ( rc == 1 && printable_ipv6 != NULL ) + { + *printable_ipv6 = string_alloc( prefix_str, NULL ); + } + + if ( sep != NULL ) *sep = '/'; + + if ( rc != 1 ) + { + msg (msglevel, "IPv6 prefix '%s': invalid IPv6 address", prefix_str); + return false; + } + + if ( netbits != NULL ) + { + *netbits = bits; + } + if ( network != NULL ) + { + *network = t_network; + } + return true; /* parsing OK, values set */ +} + +static bool ipv6_addr_safe_hexplusbits( const char * ipv6_prefix_spec ) +{ + struct in6_addr t_addr; + unsigned int t_bits; + + return get_ipv6_addr( ipv6_prefix_spec, &t_addr, &t_bits, NULL, M_WARN ); +} + static char * string_substitute (const char *src, int from, int to, struct gc_arena *gc) { @@ -998,6 +1084,8 @@ show_p2mp_parms (const struct options *o) #if P2MP_SERVER msg (D_SHOW_PARMS, " server_network = %s", print_in_addr_t (o->server_network, 0, &gc)); msg (D_SHOW_PARMS, " server_netmask = %s", print_in_addr_t (o->server_netmask, 0, &gc)); + msg (D_SHOW_PARMS, " server_network_ipv6 = %s", print_in6_addr (o->server_network_ipv6, 0, &gc) ); + SHOW_INT (server_netbits_ipv6); msg (D_SHOW_PARMS, " server_bridge_ip = %s", print_in_addr_t (o->server_bridge_ip, 0, &gc)); msg (D_SHOW_PARMS, " server_bridge_netmask = %s", print_in_addr_t (o->server_bridge_netmask, 0, &gc)); msg (D_SHOW_PARMS, " server_bridge_pool_start = %s", print_in_addr_t (o->server_bridge_pool_start, 0, &gc)); @@ -1018,6 +1106,9 @@ show_p2mp_parms (const struct options *o) msg (D_SHOW_PARMS, " ifconfig_pool_netmask = %s", print_in_addr_t (o->ifconfig_pool_netmask, 0, &gc)); SHOW_STR (ifconfig_pool_persist_filename); SHOW_INT (ifconfig_pool_persist_refresh_freq); + SHOW_BOOL (ifconfig_ipv6_pool_defined); + msg (D_SHOW_PARMS, " ifconfig_ipv6_pool_base = %s", print_in6_addr (o->ifconfig_ipv6_pool_base, 0, &gc)); + SHOW_INT (ifconfig_ipv6_pool_netbits); SHOW_INT (n_bcast_buf); SHOW_INT (tcp_queue_limit); SHOW_INT (real_hash_size); @@ -1031,6 +1122,9 @@ show_p2mp_parms (const struct options *o) SHOW_BOOL (push_ifconfig_defined); msg (D_SHOW_PARMS, " push_ifconfig_local = %s", print_in_addr_t (o->push_ifconfig_local, 0, &gc)); msg (D_SHOW_PARMS, " push_ifconfig_remote_netmask = %s", print_in_addr_t (o->push_ifconfig_remote_netmask, 0, &gc)); + SHOW_BOOL (push_ifconfig_ipv6_defined); + msg (D_SHOW_PARMS, " push_ifconfig_ipv6_local = %s/%d", print_in6_addr (o->push_ifconfig_ipv6_local, 0, &gc), o->push_ifconfig_ipv6_netbits ); + msg (D_SHOW_PARMS, " push_ifconfig_ipv6_remote = %s", print_in6_addr (o->push_ifconfig_ipv6_remote, 0, &gc)); SHOW_BOOL (enable_c2c); SHOW_BOOL (duplicate_cn); SHOW_INT (cf_max); @@ -1085,6 +1179,25 @@ option_iroute (struct options *o, o->iroutes = ir; } +static void +option_iroute_ipv6 (struct options *o, + const char *prefix_str, + int msglevel) +{ + struct iroute_ipv6 *ir; + + ALLOC_OBJ_GC (ir, struct iroute_ipv6, &o->gc); + + if ( get_ipv6_addr (prefix_str, &ir->network, &ir->netbits, NULL, msglevel ) < 0 ) + { + msg (msglevel, "in --iroute-ipv6 %s: Bad IPv6 prefix specification", + prefix_str); + return; + } + + ir->next = o->iroutes_ipv6; + o->iroutes_ipv6 = ir; +} #endif /* P2MP_SERVER */ #endif /* P2MP */ @@ -1122,6 +1235,13 @@ rol_check_alloc (struct options *options) options->routes = new_route_option_list (options->max_routes, &options->gc); } +void +rol6_check_alloc (struct options *options) +{ + if (!options->routes_ipv6) + options->routes_ipv6 = new_route_ipv6_option_list (options->max_routes, &options->gc); +} + #ifdef ENABLE_DEBUG static void show_connection_entry (const struct connection_entry *o) @@ -1212,6 +1332,9 @@ show_settings (const struct options *o) SHOW_STR (ifconfig_remote_netmask); SHOW_BOOL (ifconfig_noexec); SHOW_BOOL (ifconfig_nowarn); + SHOW_STR (ifconfig_ipv6_local); + SHOW_INT (ifconfig_ipv6_netbits); + SHOW_STR (ifconfig_ipv6_remote); #ifdef HAVE_GETTIMEOFDAY SHOW_INT (shaper); @@ -1915,8 +2038,10 @@ options_postprocess_verify_ce (const struct options *options, const struct conne if (options->connection_list) msg (M_USAGE, "<connection> cannot be used with --mode server"); #endif +#if 0 if (options->tun_ipv6) msg (M_USAGE, "--tun-ipv6 cannot be used with --mode server"); +#endif if (options->shaper) msg (M_USAGE, "--shaper cannot be used with --mode server"); if (options->inetd) @@ -1949,6 +2074,11 @@ options_postprocess_verify_ce (const struct options *options, const struct conne msg (M_USAGE, "--up-delay cannot be used with --mode server"); if (!options->ifconfig_pool_defined && options->ifconfig_pool_persist_filename) msg (M_USAGE, "--ifconfig-pool-persist must be used with --ifconfig-pool"); + if (options->ifconfig_ipv6_pool_defined && !options->ifconfig_ipv6_local ) + msg (M_USAGE, "--ifconfig-ipv6-pool needs --ifconfig-ipv6"); + if (options->ifconfig_ipv6_local && !options->tun_ipv6 ) + msg (M_INFO, "Warning: --ifconfig-ipv6 without --tun-ipv6 will not do IPv6"); + if (options->auth_user_pass_file) msg (M_USAGE, "--auth-user-pass cannot be used with --mode server (it should be used on the client side only)"); if (options->ccd_exclusive && !options->client_config_dir) @@ -1980,6 +2110,8 @@ options_postprocess_verify_ce (const struct options *options, const struct conne */ if (options->ifconfig_pool_defined || options->ifconfig_pool_persist_filename) msg (M_USAGE, "--ifconfig-pool/--ifconfig-pool-persist requires --mode server"); + if (options->ifconfig_ipv6_pool_defined) + msg (M_USAGE, "--ifconfig-ipv6-pool requires --mode server"); if (options->real_hash_size != defaults.real_hash_size || options->virtual_hash_size != defaults.virtual_hash_size) msg (M_USAGE, "--hash-size requires --mode server"); @@ -2525,6 +2657,8 @@ options_string (const struct options *o, o->topology, o->ifconfig_local, o->ifconfig_remote_netmask, + o->ifconfig_ipv6_local, + o->ifconfig_ipv6_remote, (in_addr_t)0, (in_addr_t)0, false, @@ -3858,6 +3992,30 @@ add_option (struct options *options, goto err; } } + else if (streq (p[0], "ifconfig-ipv6") && p[1] && p[2] ) + { + unsigned int netbits; + char * ipv6_local; + + VERIFY_PERMISSION (OPT_P_UP); + if ( get_ipv6_addr( p[1], NULL, &netbits, &ipv6_local, msglevel ) && + ipv6_addr_safe( p[2] ) ) + { + if ( netbits < 64 || netbits > 124 ) + { + msg( msglevel, "ifconfig-ipv6: /netbits must be between 64 and 124, not '/%d'", netbits ); + goto err; + } + options->ifconfig_ipv6_local = ipv6_local; + options->ifconfig_ipv6_netbits = netbits; + options->ifconfig_ipv6_remote = p[2]; + } + else + { + msg (msglevel, "ifconfig-ipv6 parms '%s' and '%s' must be valid addresses", p[1], p[2]); + goto err; + } + } else if (streq (p[0], "ifconfig-noexec")) { VERIFY_PERMISSION (OPT_P_UP); @@ -4658,6 +4816,26 @@ add_option (struct options *options, } add_route_to_option_list (options->routes, p[1], p[2], p[3], p[4]); } + else if (streq (p[0], "route-ipv6") && p[1]) + { + VERIFY_PERMISSION (OPT_P_ROUTE); + rol6_check_alloc (options); + if (pull_mode) + { + if (!ipv6_addr_safe_hexplusbits (p[1])) + { + msg (msglevel, "route-ipv6 parameter network/IP '%s' must be a valid address", p[1]); + goto err; + } + if (p[2] && !ipv6_addr_safe (p[2])) + { + msg (msglevel, "route-ipv6 parameter gateway '%s' must be a valid address", p[2]); + goto err; + } + /* p[3] is metric, if present */ + } + add_route_ipv6_to_option_list (options->routes_ipv6, p[1], p[2], p[3]); + } else if (streq (p[0], "max-routes") && p[1]) { int max_routes; @@ -4869,6 +5047,33 @@ add_option (struct options *options, } } } + else if (streq (p[0], "server-ipv6") && p[1] ) + { + const int lev = M_WARN; + struct in6_addr network; + unsigned int netbits = 0; + + VERIFY_PERMISSION (OPT_P_GENERAL); + if ( ! get_ipv6_addr (p[1], &network, &netbits, NULL, lev) ) + { + msg (msglevel, "error parsing --server-ipv6 parameter"); + goto err; + } + if ( netbits != 64 ) + { + msg( msglevel, "--server-ipv6 settings: only /64 supported right now (not /%d)", netbits ); + goto err; + } + options->server_ipv6_defined = true; + options->server_network_ipv6 = network; + options->server_netbits_ipv6 = netbits; + + if (p[2]) /* no "nopool" options or similar for IPv6 */ + { + msg (msglevel, "error parsing --server-ipv6: %s is not a recognized flag", p[3]); + goto err; + } + } else if (streq (p[0], "server-bridge") && p[1] && p[2] && p[3] && p[4]) { const int lev = M_WARN; @@ -4953,6 +5158,28 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_GENERAL); options->topology = TOP_P2P; } + else if (streq (p[0], "ifconfig-ipv6-pool") && p[1] ) + { + const int lev = M_WARN; + struct in6_addr network; + unsigned int netbits = 0; + + VERIFY_PERMISSION (OPT_P_GENERAL); + if ( ! get_ipv6_addr (p[1], &network, &netbits, NULL, lev ) ) + { + msg (msglevel, "error parsing --ifconfig-ipv6-pool parameters"); + goto err; + } + if ( netbits != 64 ) + { + msg( msglevel, "--ifconfig-ipv6-pool settings: only /64 supported right now (not /%d)", netbits ); + goto err; + } + + options->ifconfig_ipv6_pool_defined = true; + options->ifconfig_ipv6_pool_base = network; + options->ifconfig_ipv6_pool_netbits = netbits; + } else if (streq (p[0], "hash-size") && p[1] && p[2]) { int real, virtual; @@ -5148,6 +5375,11 @@ add_option (struct options *options, } option_iroute (options, p[1], netmask, msglevel); } + else if (streq (p[0], "iroute-ipv6") && p[1]) + { + VERIFY_PERMISSION (OPT_P_INSTANCE); + option_iroute_ipv6 (options, p[1], msglevel); + } else if (streq (p[0], "ifconfig-push") && p[1] && p[2]) { in_addr_t local, remote_netmask; @@ -5186,6 +5418,43 @@ add_option (struct options *options, goto err; } } + else if (streq (p[0], "ifconfig-ipv6-push") && p[1] ) + { + struct in6_addr local, remote; + unsigned int netbits; + + VERIFY_PERMISSION (OPT_P_INSTANCE); + + if ( ! get_ipv6_addr( p[1], &local, &netbits, NULL, msglevel ) ) + { + msg (msglevel, "cannot parse --ifconfig-ipv6-push addresses"); + goto err; + } + + if ( p[2] ) + { + if ( !get_ipv6_addr( p[2], &remote, NULL, NULL, msglevel ) ) + { + msg( msglevel, "cannot parse --ifconfig-ipv6-push addresses"); + goto err; + } + } + else + { + if ( ! options->ifconfig_ipv6_local || + ! get_ipv6_addr( options->ifconfig_ipv6_local, &remote, + NULL, NULL, msglevel ) ) + { + msg( msglevel, "second argument to --ifconfig-ipv6-push missing and no global --ifconfig-ipv6 address set"); + goto err; + } + } + + options->push_ifconfig_ipv6_defined = true; + options->push_ifconfig_ipv6_local = local; + options->push_ifconfig_ipv6_netbits = netbits; + options->push_ifconfig_ipv6_remote = remote; + } else if (streq (p[0], "disable")) { VERIFY_PERMISSION (OPT_P_INSTANCE); @@ -205,6 +205,9 @@ struct options int topology; /* one of the TOP_x values from proto.h */ const char *ifconfig_local; const char *ifconfig_remote_netmask; + const char *ifconfig_ipv6_local; + int ifconfig_ipv6_netbits; + const char *ifconfig_ipv6_remote; bool ifconfig_noexec; bool ifconfig_nowarn; #ifdef HAVE_GETTIMEOFDAY @@ -326,6 +329,7 @@ struct options bool route_delay_defined; int max_routes; struct route_option_list *routes; + struct route_ipv6_option_list *routes_ipv6; /* IPv6 */ bool route_nopull; bool route_gateway_via_dhcp; bool allow_pull_fqdn; /* as a client, allow server to push a FQDN for certain parameters */ @@ -361,6 +365,9 @@ struct options bool server_defined; in_addr_t server_network; in_addr_t server_netmask; + bool server_ipv6_defined; /* IPv6 */ + struct in6_addr server_network_ipv6; /* IPv6 */ + unsigned int server_netbits_ipv6; /* IPv6 */ # define SF_NOPOOL (1<<0) # define SF_TCP_NODELAY_HELPER (1<<1) @@ -382,6 +389,11 @@ struct options in_addr_t ifconfig_pool_netmask; const char *ifconfig_pool_persist_filename; int ifconfig_pool_persist_refresh_freq; + + bool ifconfig_ipv6_pool_defined; /* IPv6 */ + struct in6_addr ifconfig_ipv6_pool_base; /* IPv6 */ + int ifconfig_ipv6_pool_netbits; /* IPv6 */ + int real_hash_size; int virtual_hash_size; const char *client_connect_script; @@ -394,12 +406,17 @@ struct options int n_bcast_buf; int tcp_queue_limit; struct iroute *iroutes; + struct iroute_ipv6 *iroutes_ipv6; /* IPv6 */ bool push_ifconfig_defined; in_addr_t push_ifconfig_local; in_addr_t push_ifconfig_remote_netmask; bool push_ifconfig_constraint_defined; in_addr_t push_ifconfig_constraint_network; in_addr_t push_ifconfig_constraint_netmask; + bool push_ifconfig_ipv6_defined; /* IPv6 */ + struct in6_addr push_ifconfig_ipv6_local; /* IPv6 */ + int push_ifconfig_ipv6_netbits; /* IPv6 */ + struct in6_addr push_ifconfig_ipv6_remote; /* IPv6 */ bool enable_c2c; bool duplicate_cn; int cf_max; @@ -722,6 +739,10 @@ void options_string_import (struct options *options, unsigned int *option_types_found, struct env_set *es); +bool get_ipv6_addr( const char * prefix_str, struct in6_addr *network, + unsigned int * netbits, char ** printable_ipv6, + int msglevel ); + /* * inline functions */ @@ -132,7 +132,10 @@ ifconfig_pool_verify_range (const int msglevel, const in_addr_t start, const in_ } struct ifconfig_pool * -ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, const bool duplicate_cn) +ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, + const bool duplicate_cn, + const bool ipv6_pool, const struct in6_addr ipv6_base, + const int ipv6_netbits ) { struct gc_arena gc = gc_new (); struct ifconfig_pool *pool = NULL; @@ -157,11 +160,31 @@ ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, const bool duplica ASSERT (0); } + /* IPv6 pools are always "INDIV" type */ + pool->ipv6 = ipv6_pool; + + if ( pool->ipv6 ) + { + pool->base_ipv6 = ipv6_base; + pool->size_ipv6 = ipv6_netbits>96? ( 1<<(128-ipv6_netbits) ) + : IFCONFIG_POOL_MAX; + + msg( D_IFCONFIG_POOL, "IFCONFIG POOL IPv6: (IPv4) size=%d, size_ipv6=%d, netbits=%d, base_ipv6=%s", + pool->size, pool->size_ipv6, ipv6_netbits, + print_in6_addr( pool->base_ipv6, 0, &gc )); + + /* the current code is very simple and assumes that the IPv6 + * pool is at least as big as the IPv4 pool, and we don't need + * to do separate math etc. for IPv6 + */ + ASSERT( pool->size < pool->size_ipv6 ); + } + ALLOC_ARRAY_CLEAR (pool->list, struct ifconfig_pool_entry, pool->size); - msg (D_IFCONFIG_POOL, "IFCONFIG POOL: base=%s size=%d", + msg (D_IFCONFIG_POOL, "IFCONFIG POOL: base=%s size=%d, ipv6=%d", print_in_addr_t (pool->base, 0, &gc), - pool->size); + pool->size, pool->ipv6 ); gc_free (&gc); return pool; @@ -181,7 +204,7 @@ ifconfig_pool_free (struct ifconfig_pool *pool) } ifconfig_pool_handle -ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, const char *common_name) +ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, struct in6_addr *remote_ipv6, const char *common_name) { int i; @@ -214,6 +237,12 @@ ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t * default: ASSERT (0); } + + /* IPv6 pools are always INDIV (--linear) */ + if ( pool->ipv6 && remote_ipv6 ) + { + *remote_ipv6 = add_in6_addr( pool->base_ipv6, i ); + } } return i; } @@ -288,6 +317,19 @@ ifconfig_pool_handle_to_ip_base (const struct ifconfig_pool* pool, ifconfig_pool return ret; } +static struct in6_addr +ifconfig_pool_handle_to_ipv6_base (const struct ifconfig_pool* pool, ifconfig_pool_handle hand) +{ + struct in6_addr ret = in6addr_any; + + /* IPv6 pools are always INDIV (--linear) */ + if (hand >= 0 && hand < pool->size_ipv6 ) + { + ret = add_in6_addr( pool->base_ipv6, hand ); + } + return ret; +} + static void ifconfig_pool_set (struct ifconfig_pool* pool, const char *cn, const in_addr_t addr, const bool fixed) { @@ -317,9 +359,20 @@ ifconfig_pool_list (const struct ifconfig_pool* pool, struct status_output *out) if (e->common_name) { const in_addr_t ip = ifconfig_pool_handle_to_ip_base (pool, i); - status_printf (out, "%s,%s", - e->common_name, - print_in_addr_t (ip, 0, &gc)); + if ( pool->ipv6 ) + { + struct in6_addr ip6 = ifconfig_pool_handle_to_ipv6_base (pool, i); + status_printf (out, "%s,%s,%s", + e->common_name, + print_in_addr_t (ip, 0, &gc), + print_in6_addr (ip6, 0, &gc)); + } + else + { + status_printf (out, "%s,%s", + e->common_name, + print_in_addr_t (ip, 0, &gc)); + } } } gc_free (&gc); @@ -409,6 +462,9 @@ ifconfig_pool_read (struct ifconfig_pool_persist *persist, struct ifconfig_pool int c = *BSTR(&in); if (c == '#' || c == ';') continue; + msg( M_INFO, "ifconfig_pool_read(), in='%s', TODO: IPv6", + BSTR(&in) ); + if (buf_parse (&in, ',', cn_buf, buf_size) && buf_parse (&in, ',', ip_buf, buf_size)) { @@ -416,6 +472,7 @@ ifconfig_pool_read (struct ifconfig_pool_persist *persist, struct ifconfig_pool const in_addr_t addr = getaddr (GETADDR_HOST_ORDER, ip_buf, 0, &succeeded, NULL); if (succeeded) { + msg( M_INFO, "succeeded -> ifconfig_pool_set()"); ifconfig_pool_set (pool, cn_buf, addr, persist->fixed); } } @@ -471,7 +528,7 @@ ifconfig_pool_test (in_addr_t start, in_addr_t end) #else cn = buf; #endif - h = ifconfig_pool_acquire (p, &local, &remote, cn); + h = ifconfig_pool_acquire (p, &local, &remote, NULL, cn); if (h < 0) break; msg (M_INFO | M_NOPREFIX, "IFCONFIG_POOL TEST pass 1: l=%s r=%s cn=%s", @@ -506,7 +563,7 @@ ifconfig_pool_test (in_addr_t start, in_addr_t end) #else cn = buf; #endif - h = ifconfig_pool_acquire (p, &local, &remote, cn); + h = ifconfig_pool_acquire (p, &local, &remote, NULL, cn); if (h < 0) break; msg (M_INFO | M_NOPREFIX, "IFCONFIG_POOL TEST pass 3: l=%s r=%s cn=%s", @@ -52,6 +52,9 @@ struct ifconfig_pool int size; int type; bool duplicate_cn; + bool ipv6; + struct in6_addr base_ipv6; + unsigned int size_ipv6; struct ifconfig_pool_entry *list; }; @@ -63,13 +66,13 @@ struct ifconfig_pool_persist typedef int ifconfig_pool_handle; -struct ifconfig_pool *ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, const bool duplicate_cn); +struct ifconfig_pool *ifconfig_pool_init (int type, in_addr_t start, in_addr_t end, const bool duplicate_cn, const bool ipv6_pool, const struct in6_addr ipv6_base, const int ipv6_netbits ); void ifconfig_pool_free (struct ifconfig_pool *pool); bool ifconfig_pool_verify_range (const int msglevel, const in_addr_t start, const in_addr_t end); -ifconfig_pool_handle ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, const char *common_name); +ifconfig_pool_handle ifconfig_pool_acquire (struct ifconfig_pool *pool, in_addr_t *local, in_addr_t *remote, struct in6_addr *remote_ipv6, const char *common_name); bool ifconfig_pool_release (struct ifconfig_pool* pool, ifconfig_pool_handle hand, const bool hard); @@ -108,6 +108,21 @@ struct openvpn_iphdr { }; /* + * IPv6 header + */ +struct openvpn_ipv6hdr { + uint8_t version_prio; + uint8_t flow_lbl[3]; + uint16_t payload_len; + uint8_t nexthdr; + uint8_t hop_limit; + + struct in6_addr saddr; + struct in6_addr daddr; +}; + + +/* * UDP header */ struct openvpn_udphdr { @@ -189,8 +189,26 @@ send_push_reply (struct context *c) const int safe_cap = BCAP (&buf) - extra; bool push_sent = false; + msg( M_INFO, "send_push_reply(): safe_cap=%d", safe_cap ); + buf_printf (&buf, "%s", cmd); + if ( c->c2.push_ifconfig_ipv6_defined ) + { + /* IPv6 is put into buffer first, could be lengthy */ + /* TODO: push "/netbits" as well, to allow non-/64 subnet sizes + * (needs changes in options.c, options.h, and other places) + */ + buf_printf( &buf, ",ifconfig-ipv6 %s %s", + print_in6_addr( c->c2.push_ifconfig_ipv6_local, 0, &gc), + print_in6_addr( c->c2.push_ifconfig_ipv6_remote, 0, &gc) ); + if (BLEN (&buf) >= safe_cap) + { + msg (M_WARN, "--push ifconfig-ipv6 option is too long"); + goto fail; + } + } + while (e) { if (e->enable) @@ -35,10 +35,12 @@ #include "socket.h" #include "manage.h" #include "win32.h" +#include "options.h" #include "memdbg.h" static void delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es); +static void delete_route_ipv6 (const struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es); static void get_bypass_addresses (struct route_bypass *rb, const unsigned int flags); #ifdef ENABLE_DEBUG @@ -68,6 +70,15 @@ new_route_option_list (const int max_routes, struct gc_arena *a) return ret; } +struct route_ipv6_option_list * +new_route_ipv6_option_list (const int max_routes, struct gc_arena *a) +{ + struct route_ipv6_option_list *ret; + ALLOC_VAR_ARRAY_CLEAR_GC (ret, struct route_ipv6_option_list, struct route_ipv6_option, max_routes, a); + ret->capacity = max_routes; + return ret; +} + struct route_option_list * clone_route_option_list (const struct route_option_list *src, struct gc_arena *a) { @@ -95,6 +106,15 @@ new_route_list (const int max_routes, struct gc_arena *a) return ret; } +struct route_ipv6_list * +new_route_ipv6_list (const int max_routes, struct gc_arena *a) +{ + struct route_ipv6_list *ret; + ALLOC_VAR_ARRAY_CLEAR_GC (ret, struct route_ipv6_list, struct route_ipv6, max_routes, a); + ret->capacity = max_routes; + return ret; +} + static const char * route_string (const struct route *r, struct gc_arena *gc) { @@ -311,6 +331,68 @@ init_route (struct route *r, return false; } +static bool +init_route_ipv6 (struct route_ipv6 *r6, + const struct route_ipv6_option *r6o, + const struct route_ipv6_list *rl6 ) +{ + r6->option = r6o; + r6->defined = false; + + if ( !get_ipv6_addr( r6o->prefix, &r6->network, &r6->netbits, NULL, M_WARN )) + goto fail; + + /* gateway */ + if (is_route_parm_defined (r6o->gateway)) + { + if ( inet_pton( AF_INET6, r6o->gateway, &r6->gateway ) != 1 ) + { + msg( M_WARN, PACKAGE_NAME "ROUTE6: cannot parse gateway spec '%s'", r6o->gateway ); + } + } + else if (rl6->remote_endpoint_defined) + { + r6->gateway = rl6->remote_endpoint_ipv6; + } + else + { + msg (M_WARN, PACKAGE_NAME " ROUTE6: " PACKAGE_NAME " needs a gateway parameter for a --route-ipv6 option and no default was specified by either --route-ipv6-gateway or --ifconfig-ipv6 options"); + goto fail; + } + + /* metric */ + + r6->metric_defined = false; + r6->metric = 0; + if (is_route_parm_defined (r6o->metric)) + { + r6->metric = atoi (r6o->metric); + if (r6->metric < 0) + { + msg (M_WARN, PACKAGE_NAME " ROUTE: route metric for network %s (%s) must be >= 0", + r6o->prefix, + r6o->metric); + goto fail; + } + r6->metric_defined = true; + } + else if (rl6->default_metric_defined) + { + r6->metric = rl6->default_metric; + r6->metric_defined = true; + } + + r6->defined = true; + + return true; + + fail: + msg (M_WARN, PACKAGE_NAME " ROUTE: failed to parse/resolve route for host/network: %s", + r6o->prefix); + r6->defined = false; + return false; +} + void add_route_to_option_list (struct route_option_list *l, const char *network, @@ -331,6 +413,23 @@ add_route_to_option_list (struct route_option_list *l, } void +add_route_ipv6_to_option_list (struct route_ipv6_option_list *l, + const char *prefix, + const char *gateway, + const char *metric) +{ + struct route_ipv6_option *ro; + if (l->n >= l->capacity) + msg (M_FATAL, PACKAGE_NAME " ROUTE: cannot add more than %d IPv6 routes -- please increase the max-routes option in the client configuration file", + l->capacity); + ro = &l->routes_ipv6[l->n]; + ro->prefix = prefix; + ro->gateway = gateway; + ro->metric = metric; + ++l->n; +} + +void clear_route_list (struct route_list *rl) { const int capacity = rl->capacity; @@ -340,6 +439,15 @@ clear_route_list (struct route_list *rl) } void +clear_route_ipv6_list (struct route_ipv6_list *rl6) +{ + const int capacity = rl6->capacity; + const size_t rl6_size = array_mult_safe (sizeof(struct route_ipv6), capacity, sizeof(struct route_ipv6_list)); + memset(rl6, 0, rl6_size); + rl6->capacity = capacity; +} + +void route_list_add_default_gateway (struct route_list *rl, struct env_set *es, const in_addr_t addr) @@ -469,6 +577,72 @@ init_route_list (struct route_list *rl, return ret; } +bool +init_route_ipv6_list (struct route_ipv6_list *rl6, + const struct route_ipv6_option_list *opt6, + const char *remote_endpoint, + int default_metric, + struct env_set *es) +{ + struct gc_arena gc = gc_new (); + bool ret = true; + + clear_route_ipv6_list (rl6); + + rl6->flags = opt6->flags; + + if (default_metric) + { + rl6->default_metric = default_metric; + rl6->default_metric_defined = true; + } + + /* "default_gateway" is stuff for "redirect-gateway", which we don't + * do for IPv6 yet -> TODO + */ + { + dmsg (D_ROUTE, "ROUTE6: default_gateway=UNDEF"); + } + + if ( is_route_parm_defined( remote_endpoint )) + { + if ( inet_pton( AF_INET6, remote_endpoint, + &rl6->remote_endpoint_ipv6) == 1 ) + { + rl6->remote_endpoint_defined = true; + } + else + { + msg (M_WARN, PACKAGE_NAME " ROUTE: failed to parse/resolve default gateway: %s", remote_endpoint); + ret = false; + } + } + else + rl6->remote_endpoint_defined = false; + + + if (!(opt6->n >= 0 && opt6->n <= rl6->capacity)) + msg (M_FATAL, PACKAGE_NAME " ROUTE6: (init) number of route options (%d) is greater than route list capacity (%d)", opt6->n, rl6->capacity); + + /* parse the routes from opt to rl6 */ + { + int i, j = 0; + for (i = 0; i < opt6->n; ++i) + { + if (!init_route_ipv6 (&rl6->routes_ipv6[j], + &opt6->routes_ipv6[i], + rl6 )) + ret = false; + else + ++j; + } + rl6->n = j; + } + + gc_free (&gc); + return ret; +} + static void add_route3 (in_addr_t network, in_addr_t netmask, @@ -714,10 +888,13 @@ undo_redirect_default_route_to_vpn (struct route_list *rl, const struct tuntap * } void -add_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flags, const struct env_set *es) +add_routes (struct route_list *rl, struct route_ipv6_list *rl6, + const struct tuntap *tt, unsigned int flags, const struct env_set *es) { - redirect_default_route_to_vpn (rl, tt, flags, es); - if (!rl->routes_added) + if (rl) + redirect_default_route_to_vpn (rl, tt, flags, es); + + if (rl && !rl->routes_added) { int i; @@ -742,12 +919,27 @@ add_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flags, } rl->routes_added = true; } + + if (rl6 && !rl6->routes_added) + { + int i; + + for (i = 0; i < rl6->n; ++i) + { + struct route_ipv6 *r = &rl6->routes_ipv6[i]; + if (flags & ROUTE_DELETE_FIRST) + delete_route_ipv6 (r, tt, flags, es); + add_route_ipv6 (r, tt, flags, es); + } + rl6->routes_added = true; + } } void -delete_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flags, const struct env_set *es) +delete_routes (struct route_list *rl, struct route_ipv6_list *rl6, + const struct tuntap *tt, unsigned int flags, const struct env_set *es) { - if (rl->routes_added) + if (rl && rl->routes_added) { int i; for (i = rl->n - 1; i >= 0; --i) @@ -757,9 +949,28 @@ delete_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flag } rl->routes_added = false; } - undo_redirect_default_route_to_vpn (rl, tt, flags, es); - clear_route_list (rl); + if ( rl ) + { + undo_redirect_default_route_to_vpn (rl, tt, flags, es); + clear_route_list (rl); + } + + if ( rl6 && rl6->routes_added ) + { + int i; + for (i = rl6->n - 1; i >= 0; --i) + { + const struct route_ipv6 *r6 = &rl6->routes_ipv6[i]; + delete_route_ipv6 (r6, tt, flags, es); + } + rl6->routes_added = false; + } + + if ( rl6 ) + { + clear_route_ipv6_list (rl6); + } } #ifdef ENABLE_DEBUG @@ -842,6 +1053,34 @@ setenv_routes (struct env_set *es, const struct route_list *rl) setenv_route (es, &rl->routes[i], i + 1); } +static void +setenv_route_ipv6 (struct env_set *es, const struct route_ipv6 *r6, int i) +{ + struct gc_arena gc = gc_new (); + if (r6->defined) + { + struct buffer name1 = alloc_buf_gc( 256, &gc ); + struct buffer val = alloc_buf_gc( 256, &gc ); + struct buffer name2 = alloc_buf_gc( 256, &gc ); + + buf_printf( &name1, "route_ipv6_network_%d", i ); + buf_printf( &val, "%s/%d", print_in6_addr( r6->network, 0, &gc ), + r6->netbits ); + setenv_str( es, BSTR(&name1), BSTR(&val) ); + + buf_printf( &name2, "route_ipv6_gateway_%d", i ); + setenv_str( es, BSTR(&name2), print_in6_addr( r6->gateway, 0, &gc )); + } + gc_free (&gc); +} +void +setenv_routes_ipv6 (struct env_set *es, const struct route_ipv6_list *rl6) +{ + int i; + for (i = 0; i < rl6->n; ++i) + setenv_route_ipv6 (es, &rl6->routes_ipv6[i], i + 1); +} + void add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es) { @@ -1035,6 +1274,176 @@ add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const s gc_free (&gc); } +void +add_route_ipv6 (struct route_ipv6 *r6, const struct tuntap *tt, unsigned int flags, const struct env_set *es) +{ + struct gc_arena gc; + struct argv argv; + + const char *network; + const char *gateway; + bool status = false; + const char *device = tt->actual_name; + int byte, bits_to_clear; + struct in6_addr network_copy = r6->network; + + if (!r6->defined) + return; + + gc_init (&gc); + argv_init (&argv); + + /* clear host bit parts of route + * (needed if routes are specified improperly, or if we need to + * explicitely setup the "connected" network routes on some OSes) + */ + byte = 15; + bits_to_clear = 128 - r6->netbits; + + while( byte >= 0 && bits_to_clear > 0 ) + { + if ( bits_to_clear >= 8 ) + { network_copy.s6_addr[byte--] = 0; bits_to_clear -= 8; } + else + { network_copy.s6_addr[byte--] &= (~0 << bits_to_clear); bits_to_clear = 0; } + } + + network = print_in6_addr( network_copy, 0, &gc); + gateway = print_in6_addr( r6->gateway, 0, &gc); + + if ( !tt->ipv6 ) + { + msg( M_INFO, "add_route_ipv6(): not adding %s/%d, no IPv6 on if %s", + network, r6->netbits, device ); + return; + } + + msg( M_INFO, "add_route_ipv6(%s/%d -> %s metric %d) dev %s", + network, r6->netbits, gateway, r6->metric, device ); + + /* + * Filter out routes which are essentially no-ops + * (not currently done for IPv6) + */ + +#if defined(TARGET_LINUX) +#ifdef CONFIG_FEATURE_IPROUTE + argv_printf (&argv, "%s -6 route add %s/%d dev %s", + iproute_path, + network, + r6->netbits, + device); + if (r6->metric_defined) + argv_printf_cat (&argv, " metric %d", r6->metric); + +#else + argv_printf (&argv, "%s -A inet6 add %s/%d dev %s", + ROUTE_PATH, + network, + r6->netbits, + device); + if (r6->metric_defined) + argv_printf_cat (&argv, " metric %d", r6->metric); +#endif /*CONFIG_FEATURE_IPROUTE*/ + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Linux route -6/-A inet6 add command failed"); + +#elif defined (WIN32) + + /* netsh interface ipv6 add route 2001:db8::/32 MyTunDevice */ + argv_printf (&argv, "%s%sc interface ipv6 add route %s/%d %s", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + network, + r6->netbits, + device); + + /* next-hop depends on TUN or TAP mode: + * - in TAP mode, we use the "real" next-hop + * - in TUN mode we use a special-case link-local address that the tapdrvr + * knows about and will answer ND (neighbor discovery) packets for + */ + if ( tt->type == DEV_TYPE_TUN ) + argv_printf_cat( &argv, " %s", "fe80::8" ); + else + argv_printf_cat( &argv, " %s", gateway ); + +#if 0 + if (r->metric_defined) + argv_printf_cat (&argv, " METRIC %d", r->metric); +#endif + + argv_msg (D_ROUTE, &argv); + + netcmd_semaphore_lock (); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add ipv6 command failed"); + netcmd_semaphore_release (); + +#elif defined (TARGET_SOLARIS) + + /* example: route add -inet6 2001:db8::/32 somegateway 0 */ + + /* for some weird reason, this does not work for me unless I set + * "metric 0" - otherwise, the routes will be nicely installed, but + * packets will just disappear somewhere. So we use "0" now... + */ + + argv_printf (&argv, "%s add -inet6 %s/%d %s 0", + ROUTE_PATH, + network, + r6->netbits, + gateway ); + + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: Solaris route add -inet6 command failed"); + +#elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) + + argv_printf (&argv, "%s add -inet6 %s/%d -iface %s", + ROUTE_PATH, + network, + r6->netbits, + device ); + + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: *BSD route add -inet6 command failed"); + +#elif defined(TARGET_DARWIN) + + argv_printf (&argv, "%s add -inet6 %s -prefixlen %d -iface %s", + ROUTE_PATH, + network, r6->netbits, device ); + + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: MacOS X route add -inet6 command failed"); + +#elif defined(TARGET_OPENBSD) + + argv_printf (&argv, "%s add -inet6 %s -prefixlen %d %s", + ROUTE_PATH, + network, r6->netbits, gateway ); + + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: OpenBSD route add -inet6 command failed"); + +#elif defined(TARGET_NETBSD) + + argv_printf (&argv, "%s add -inet6 %s/%d %s", + ROUTE_PATH, + network, r6->netbits, gateway ); + + argv_msg (D_ROUTE, &argv); + status = openvpn_execve_check (&argv, es, 0, "ERROR: NetBSD route add -inet6 command failed"); + +#else + msg (M_FATAL, "Sorry, but I don't know how to do 'route ipv6' commands on this operating system. Try putting your routes in a --route-up script"); +#endif + + r6->defined = status; + argv_reset (&argv); + gc_free (&gc); +} + static void delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es) { @@ -1174,6 +1583,142 @@ delete_route (const struct route *r, const struct tuntap *tt, unsigned int flags gc_free (&gc); } +static void +delete_route_ipv6 (const struct route_ipv6 *r6, const struct tuntap *tt, unsigned int flags, const struct env_set *es) +{ + struct gc_arena gc; + struct argv argv; + const char *network; + const char *gateway; + const char *device = tt->actual_name; + + if (!r6->defined) + return; + + gc_init (&gc); + argv_init (&argv); + + network = print_in6_addr( r6->network, 0, &gc); + gateway = print_in6_addr( r6->gateway, 0, &gc); + + if ( !tt->ipv6 ) + { + msg( M_INFO, "delete_route_ipv6(): not deleting %s/%d, no IPv6 on if %s", + network, r6->netbits, device ); + return; + } + + msg( M_INFO, "delete_route_ipv6(%s/%d)", network, r6->netbits ); + +#if defined(TARGET_LINUX) +#ifdef CONFIG_FEATURE_IPROUTE + argv_printf (&argv, "%s -6 route del %s/%d dev %s", + iproute_path, + network, + r6->netbits, + device); +#else + argv_printf (&argv, "%s -A inet6 del %s/%d dev %s", + ROUTE_PATH, + network, + r6->netbits, + device); +#endif /*CONFIG_FEATURE_IPROUTE*/ + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: Linux route -6/-A inet6 del command failed"); + +#elif defined (WIN32) + + /* netsh interface ipv6 delete route 2001:db8::/32 MyTunDevice */ + argv_printf (&argv, "%s%sc interface ipv6 delete route %s/%d %s", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + network, + r6->netbits, + device); + + /* next-hop depends on TUN or TAP mode: + * - in TAP mode, we use the "real" next-hop + * - in TUN mode we use a special-case link-local address that the tapdrvr + * knows about and will answer ND (neighbor discovery) packets for + * (and "route deletion without specifying next-hop" does not work...) + */ + if ( tt->type == DEV_TYPE_TUN ) + argv_printf_cat( &argv, " %s", "fe80::8" ); + else + argv_printf_cat( &argv, " %s", gateway ); + +#if 0 + if (r->metric_defined) + argv_printf_cat (&argv, "METRIC %d", r->metric); +#endif + + argv_msg (D_ROUTE, &argv); + + netcmd_semaphore_lock (); + openvpn_execve_check (&argv, es, 0, "ERROR: Windows route add ipv6 command failed"); + netcmd_semaphore_release (); + +#elif defined (TARGET_SOLARIS) + + /* example: route delete -inet6 2001:db8::/32 somegateway */ + /* GERT-TODO: this is untested, but should work */ + + argv_printf (&argv, "%s delete -inet6 %s/%d %s", + ROUTE_PATH, + network, + r6->netbits, + gateway ); + + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: Solaris route delete -inet6 command failed"); + +#elif defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) + + argv_printf (&argv, "%s delete -inet6 %s/%d -iface %s", + ROUTE_PATH, + network, + r6->netbits, + device ); + + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: *BSD route delete -inet6 command failed"); + +#elif defined(TARGET_DARWIN) + + argv_printf (&argv, "%s delete -inet6 %s -prefixlen %d -iface %s", + ROUTE_PATH, + network, r6->netbits, device ); + + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: *BSD route delete -inet6 command failed"); + +#elif defined(TARGET_OPENBSD) + + argv_printf (&argv, "%s delete -inet6 %s -prefixlen %d %s", + ROUTE_PATH, + network, r6->netbits, gateway ); + + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: OpenBSD route delete -inet6 command failed"); + +#elif defined(TARGET_NETBSD) + + argv_printf (&argv, "%s delete -inet6 %s/%d %s", + ROUTE_PATH, + network, r6->netbits, gateway ); + + argv_msg (D_ROUTE, &argv); + openvpn_execve_check (&argv, es, 0, "ERROR: NetBSD route delete -inet6 command failed"); + +#else + msg (M_FATAL, "Sorry, but I don't know how to do 'route ipv6' commands on this operating system. Try putting your routes in a --route-down script"); +#endif + + argv_reset (&argv); + gc_free (&gc); +} + /* * The --redirect-gateway option requires OS-specific code below * to get the current default gateway. @@ -92,6 +92,19 @@ struct route_option_list { struct route_option routes[EMPTY_ARRAY_SIZE]; }; +struct route_ipv6_option { + const char *prefix; /* e.g. "2001:db8:1::/64" */ + const char *gateway; /* e.g. "2001:db8:0::2" */ + const char *metric; /* e.g. "5" */ +}; + +struct route_ipv6_option_list { + unsigned int flags; + int capacity; + int n; + struct route_ipv6_option routes_ipv6[EMPTY_ARRAY_SIZE]; +}; + struct route { bool defined; const struct route_option *option; @@ -113,6 +126,31 @@ struct route_list { struct route routes[EMPTY_ARRAY_SIZE]; }; +struct route_ipv6 { + bool defined; + const struct route_ipv6_option *option; + struct in6_addr network; + unsigned int netbits; + struct in6_addr gateway; + bool metric_defined; + int metric; +}; + +struct route_ipv6_list { + bool routes_added; + unsigned int flags; + int default_metric; + bool default_metric_defined; + struct in6_addr remote_endpoint_ipv6; + bool remote_endpoint_defined; + bool did_redirect_default_gateway; /* TODO (?) */ + bool did_local; /* TODO (?) */ + int capacity; + int n; + struct route_ipv6 routes_ipv6[EMPTY_ARRAY_SIZE]; +}; + + #if P2MP /* internal OpenVPN route */ struct iroute { @@ -120,15 +158,24 @@ struct iroute { int netbits; struct iroute *next; }; + +struct iroute_ipv6 { + struct in6_addr network; + unsigned int netbits; + struct iroute_ipv6 *next; +}; #endif struct route_option_list *new_route_option_list (const int max_routes, struct gc_arena *a); +struct route_ipv6_option_list *new_route_ipv6_option_list (const int max_routes, struct gc_arena *a); struct route_option_list *clone_route_option_list (const struct route_option_list *src, struct gc_arena *a); void copy_route_option_list (struct route_option_list *dest, const struct route_option_list *src); struct route_list *new_route_list (const int max_routes, struct gc_arena *a); +struct route_ipv6_list *new_route_ipv6_list (const int max_routes, struct gc_arena *a); void add_route (struct route *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es); +void add_route_ipv6 (struct route_ipv6 *r, const struct tuntap *tt, unsigned int flags, const struct env_set *es); void add_route_to_option_list (struct route_option_list *l, const char *network, @@ -136,6 +183,11 @@ void add_route_to_option_list (struct route_option_list *l, const char *gateway, const char *metric); +void add_route_ipv6_to_option_list (struct route_ipv6_option_list *l, + const char *prefix, + const char *gateway, + const char *metric); + bool init_route_list (struct route_list *rl, const struct route_option_list *opt, const char *remote_endpoint, @@ -143,21 +195,30 @@ bool init_route_list (struct route_list *rl, in_addr_t remote_host, struct env_set *es); +bool init_route_ipv6_list (struct route_ipv6_list *rl6, + const struct route_ipv6_option_list *opt6, + const char *remote_endpoint, + int default_metric, + struct env_set *es); + void route_list_add_default_gateway (struct route_list *rl, struct env_set *es, const in_addr_t addr); void add_routes (struct route_list *rl, + struct route_ipv6_list *rl6, const struct tuntap *tt, unsigned int flags, const struct env_set *es); void delete_routes (struct route_list *rl, + struct route_ipv6_list *rl6, const struct tuntap *tt, unsigned int flags, const struct env_set *es); void setenv_routes (struct env_set *es, const struct route_list *rl); +void setenv_routes_ipv6 (struct env_set *es, const struct route_ipv6_list *rl6); bool is_special_addr (const char *addr_str); @@ -543,6 +543,24 @@ ip_addr_dotted_quad_safe (const char *dotted_quad) } } +bool +ipv6_addr_safe (const char *ipv6_text_addr) +{ + /* verify non-NULL */ + if (!ipv6_text_addr) + return false; + + /* verify length is within limits */ + if (strlen (ipv6_text_addr) > INET6_ADDRSTRLEN ) + return false; + + /* verify that string will convert to IPv6 address */ + { + struct in6_addr a6; + return inet_pton( AF_INET6, ipv6_text_addr, &a6 ) == 1; + } +} + static bool dns_addr_safe (const char *addr) { @@ -2578,6 +2596,55 @@ print_in_addr_t (in_addr_t addr, unsigned int flags, struct gc_arena *gc) return BSTR (&out); } +/* + * Convert an in6_addr in host byte order + * to an ascii representation of an IPv6 address + */ +const char * +print_in6_addr (struct in6_addr a6, unsigned int flags, struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (64, gc); + char tmp_out_buf[64]; /* inet_ntop wants pointer to buffer */ + + if ( memcmp(&a6, &in6addr_any, sizeof(a6)) != 0 || + !(flags & IA_EMPTY_IF_UNDEF)) + { + inet_ntop (AF_INET6, &a6, tmp_out_buf, sizeof(tmp_out_buf)-1); + buf_printf (&out, "%s", tmp_out_buf ); + } + return BSTR (&out); +} + +/* add some offset to an ipv6 address + * (add in steps of 32 bits, taking overflow into next round) + */ +#ifndef s6_addr32 +# ifdef TARGET_SOLARIS +# define s6_addr32 _S6_un._S6_u32 +# else +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif +#ifndef UINT32_MAX +# define UINT32_MAX (4294967295U) +#endif +struct in6_addr add_in6_addr( struct in6_addr base, uint32_t add ) +{ + int i; + uint32_t h; + + for( i=3; i>=0 && add > 0 ; i-- ) + { + h = ntohl( base.s6_addr32[i] ); + base.s6_addr32[i] = htonl( (h+add) & UINT32_MAX ); + /* 32-bit overrun? + * caveat: can't do "h+add > UINT32_MAX" with 32bit math! + */ + add = ( h > UINT32_MAX - add )? 1: 0; + } + return base; +} + /* set environmental variables for ip/port in *addr */ void setenv_sockaddr (struct env_set *es, const char *name_prefix, const struct openvpn_sockaddr *addr, const bool flags) @@ -3081,6 +3148,58 @@ link_socket_write_udp_posix_sendmsg (struct link_socket *sock, #ifdef WIN32 +/* + * inet_ntop() and inet_pton() wrap-implementations using + * WSAAddressToString() and WSAStringToAddress() functions + */ +const char * +inet_ntop(int af, const void *src, char *dst, socklen_t size) +{ + struct sockaddr_storage ss; + unsigned long s = size; + + CLEAR(ss); + ss.ss_family = af; + + switch(af) { + case AF_INET: + ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src; + break; + default: + ASSERT (0); + } + // cannot direclty use &size because of strict aliasing rules + return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)? + dst : NULL; +} + +int +inet_pton(int af, const char *src, void *dst) +{ + struct sockaddr_storage ss; + int size = sizeof(ss); + char src_copy[INET6_ADDRSTRLEN+1]; + + CLEAR(ss); + // stupid non-const API + strncpynt(src_copy, src, INET6_ADDRSTRLEN+1); + + if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) { + switch(af) { + case AF_INET: + *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + case AF_INET6: + *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + } + return 0; +} + int socket_recv_queue (struct link_socket *sock, int maxsize) { @@ -368,6 +368,8 @@ const char *print_link_socket_actual (const struct link_socket_actual *act, #define IA_EMPTY_IF_UNDEF (1<<0) #define IA_NET_ORDER (1<<1) const char *print_in_addr_t (in_addr_t addr, unsigned int flags, struct gc_arena *gc); +const char *print_in6_addr (struct in6_addr addr6, unsigned int flags, struct gc_arena *gc); +struct in6_addr add_in6_addr( struct in6_addr base, uint32_t add ); #define SA_IP_PORT (1<<0) #define SA_SET_IF_NONZERO (1<<1) @@ -427,6 +429,7 @@ int openvpn_inet_aton (const char *dotted_quad, struct in_addr *addr); bool ip_addr_dotted_quad_safe (const char *dotted_quad); bool ip_or_dns_addr_safe (const char *addr, const bool allow_fqdn); bool mac_addr_safe (const char *mac_addr); +bool ipv6_addr_safe (const char *ipv6_text_addr); socket_descriptor_t create_socket_tcp (void); @@ -56,13 +56,14 @@ static void netsh_ifconfig (const struct tuntap_options *to, const in_addr_t ip, const in_addr_t netmask, const unsigned int flags); +static void netsh_command (const struct argv *a, int n); static const char *netsh_get_id (const char *dev_node, struct gc_arena *gc); #endif #ifdef TARGET_SOLARIS -static void solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual); +static void solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual, bool unplumb_inet6); #include <stropts.h> #endif @@ -129,30 +130,6 @@ guess_tuntap_dev (const char *dev, 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)"; @@ -423,6 +400,8 @@ init_tun (const char *dev, /* --dev option */ int topology, /* one of the TOP_x values */ const char *ifconfig_local_parm, /* --ifconfig parm 1 */ const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */ + const char *ifconfig_ipv6_local_parm, /* --ifconfig parm 1 IPv6 */ + const char *ifconfig_ipv6_remote_parm, /* --ifconfig parm 2 IPv6 */ in_addr_t local_public, in_addr_t remote_public, const bool strict_warn, @@ -537,6 +516,40 @@ init_tun (const char *dev, /* --dev option */ tt->did_ifconfig_setup = true; } + + if (ifconfig_ipv6_local_parm && ifconfig_ipv6_remote_parm) + { + const char *ifconfig_ipv6_local = NULL; + const char *ifconfig_ipv6_remote = NULL; + + /* + * Convert arguments to binary IPv6 addresses. + */ + + if ( inet_pton( AF_INET6, ifconfig_ipv6_local_parm, &tt->local_ipv6 ) != 1 || + inet_pton( AF_INET6, ifconfig_ipv6_remote_parm, &tt->remote_ipv6 ) != 1 ) + { + msg( M_FATAL, "init_tun: problem converting IPv6 ifconfig addresses %s and %s to binary", ifconfig_ipv6_local_parm, ifconfig_ipv6_remote_parm ); + } + tt->netbits_ipv6 = 64; + + /* + * Set ifconfig parameters + */ + ifconfig_ipv6_local = print_in6_addr (tt->local_ipv6, 0, &gc); + ifconfig_ipv6_remote = print_in6_addr (tt->remote_ipv6, 0, &gc); + + /* + * Set environmental variables with ifconfig parameters. + */ + if (es) + { + setenv_str (es, "ifconfig_ipv6_local", ifconfig_ipv6_local); + setenv_str (es, "ifconfig_ipv6_remote", ifconfig_ipv6_remote); + } + tt->did_ifconfig_ipv6_setup = true; + } + gc_free (&gc); return tt; } @@ -559,6 +572,28 @@ init_tun_post (struct tuntap *tt, #endif } +#if defined(TARGET_WIN32) || \ + defined(TARGET_DARWIN) || defined(TARGET_NETBSD) || defined(TARGET_OPENBSD) + +/* some of the platforms will auto-add a "network route" pointing + * to the interface on "ifconfig tunX 2001:db8::1/64", others need + * an extra call to "route add..." + * -> helper function to simplify code below + */ +void add_route_connected_v6_net(struct tuntap * tt, + const struct env_set *es) +{ + struct route_ipv6 r6; + + r6.defined = true; + r6.network = tt->local_ipv6; + r6.netbits = tt->netbits_ipv6; + r6.gateway = tt->local_ipv6; + add_route_ipv6 (&r6, tt, 0, es); +} +#endif + + /* execute the ifconfig command through the shell */ void do_ifconfig (struct tuntap *tt, @@ -574,10 +609,16 @@ do_ifconfig (struct tuntap *tt, const char *ifconfig_local = NULL; const char *ifconfig_remote_netmask = NULL; const char *ifconfig_broadcast = NULL; + const char *ifconfig_ipv6_local = NULL; + const char *ifconfig_ipv6_remote = NULL; + bool do_ipv6 = false; struct argv argv; argv_init (&argv); + msg( M_INFO, "do_ifconfig, tt->ipv6=%d, tt->did_ifconfig_ipv6_setup=%d", + tt->ipv6, tt->did_ifconfig_ipv6_setup ); + /* * We only handle TUN/TAP devices here, not --dev null devices. */ @@ -589,6 +630,13 @@ do_ifconfig (struct tuntap *tt, ifconfig_local = print_in_addr_t (tt->local, 0, &gc); ifconfig_remote_netmask = print_in_addr_t (tt->remote_netmask, 0, &gc); + if ( tt->ipv6 && tt->did_ifconfig_ipv6_setup ) + { + ifconfig_ipv6_local = print_in6_addr (tt->local_ipv6, 0, &gc); + ifconfig_ipv6_remote = print_in6_addr (tt->remote_ipv6, 0, &gc); + do_ipv6 = true; + } + /* * If TAP-style device, generate broadcast address. */ @@ -647,7 +695,19 @@ do_ifconfig (struct tuntap *tt, argv_msg (M_INFO, &argv); openvpn_execve_check (&argv, es, S_FATAL, "Linux ip addr add failed"); } - tt->did_ifconfig = true; + if ( do_ipv6 ) + { + argv_printf( &argv, + "%s -6 addr add %s/%d dev %s", + iproute_path, + ifconfig_ipv6_local, + tt->netbits_ipv6, + actual + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ip -6 addr add failed"); + } + tt->did_ifconfig = true; #else if (tun) argv_printf (&argv, @@ -670,6 +730,18 @@ do_ifconfig (struct tuntap *tt, ); argv_msg (M_INFO, &argv); openvpn_execve_check (&argv, es, S_FATAL, "Linux ifconfig failed"); + if ( do_ipv6 ) + { + argv_printf (&argv, + "%s %s inet6 add %s/%d", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6 + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "Linux ifconfig inet6 failed"); + } tt->did_ifconfig = true; #endif /*CONFIG_FEATURE_IPROUTE*/ @@ -693,7 +765,7 @@ do_ifconfig (struct tuntap *tt, argv_msg (M_INFO, &argv); if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig phase-1 failed")) - solaris_error_close (tt, es, actual); + solaris_error_close (tt, es, actual, false); argv_printf (&argv, "%s %s netmask 255.255.255.255", @@ -725,7 +797,53 @@ do_ifconfig (struct tuntap *tt, argv_msg (M_INFO, &argv); if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig phase-2 failed")) - solaris_error_close (tt, es, actual); + solaris_error_close (tt, es, actual, false); + + if ( do_ipv6 ) + { + argv_printf (&argv, "%s %s inet6 unplumb", + IFCONFIG_PATH, actual ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, NULL); + + if ( tt->type == DEV_TYPE_TUN ) + { + argv_printf (&argv, + "%s %s inet6 plumb %s/%d %s up", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6, + ifconfig_ipv6_remote + ); + } + else /* tap mode */ + { + /* base IPv6 tap interface needs to be brought up first + */ + argv_printf (&argv, "%s %s inet6 plumb up", + IFCONFIG_PATH, actual ); + argv_msg (M_INFO, &argv); + if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig IPv6 (prepare) failed")) + solaris_error_close (tt, es, actual, true); + + /* we might need to do "ifconfig %s inet6 auto-dhcp drop" + * after the system has noticed the interface and fired up + * the DHCPv6 client - but this takes quite a while, and the + * server will ignore the DHCPv6 packets anyway. So we don't. + */ + + /* static IPv6 addresses need to go to a subinterface (tap0:1) + */ + argv_printf (&argv, + "%s %s inet6 addif %s/%d up", + IFCONFIG_PATH, actual, + ifconfig_ipv6_local, tt->netbits_ipv6 ); + } + argv_msg (M_INFO, &argv); + if (!openvpn_execve_check (&argv, es, 0, "Solaris ifconfig IPv6 failed")) + solaris_error_close (tt, es, actual, true); + } if (!tun && tt->topology == TOP_SUBNET) { @@ -787,10 +905,42 @@ do_ifconfig (struct tuntap *tt, ); argv_msg (M_INFO, &argv); openvpn_execve_check (&argv, es, S_FATAL, "OpenBSD ifconfig failed"); + if ( do_ipv6 ) + { + argv_printf (&argv, + "%s %s inet6 %s/%d", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6 + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "OpenBSD ifconfig inet6 failed"); + + /* and, hooray, we explicitely need to add a route... */ + add_route_connected_v6_net(tt, es); + } tt->did_ifconfig = true; #elif defined(TARGET_NETBSD) +/* whether or not NetBSD can do IPv6 can be seen by the availability of + * the TUNSIFHEAD ioctl() - see next TARGET_NETBSD block for more details + */ +#ifdef TUNSIFHEAD +# define NETBSD_MULTI_AF +#endif + + /* as on OpenBSD and Darwin, destroy and re-create tun<x> interface + */ + argv_printf (&argv, "%s %s destroy", IFCONFIG_PATH, actual ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, "NetBSD ifconfig destroy failed"); + + argv_printf (&argv, "%s %s create", IFCONFIG_PATH, actual ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "NetBSD ifconfig create failed"); + if (tun) argv_printf (&argv, "%s %s %s %s mtu %d netmask 255.255.255.255 up", @@ -817,6 +967,27 @@ do_ifconfig (struct tuntap *tt, ); argv_msg (M_INFO, &argv); openvpn_execve_check (&argv, es, S_FATAL, "NetBSD ifconfig failed"); + + if ( do_ipv6 ) + { +#ifdef NETBSD_MULTI_AF + argv_printf (&argv, + "%s %s inet6 %s/%d", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6 + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "NetBSD ifconfig inet6 failed"); + + /* and, hooray, we explicitely need to add a route... */ + add_route_connected_v6_net(tt, es); +#else + msg( M_INFO, "no IPv6 support for tun interfaces on NetBSD before 4.0 (if your system is newer, recompile openvpn)" ); + tt->ipv6 = false; +#endif + } tt->did_ifconfig = true; #elif defined(TARGET_DARWIN) @@ -882,6 +1053,22 @@ do_ifconfig (struct tuntap *tt, add_route (&r, tt, 0, es); } + if ( do_ipv6 ) + { + argv_printf (&argv, + "%s %s inet6 %s/%d", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6 + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "MacOS X ifconfig inet6 failed"); + + /* and, hooray, we explicitely need to add a route... */ + add_route_connected_v6_net(tt, es); + } + #elif defined(TARGET_FREEBSD)||defined(TARGET_DRAGONFLY) /* example: ifconfig tun2 10.2.0.2 10.2.0.1 mtu 1450 netmask 255.255.255.255 up */ @@ -920,6 +1107,19 @@ do_ifconfig (struct tuntap *tt, add_route (&r, tt, 0, es); } + if ( do_ipv6 ) + { + argv_printf (&argv, + "%s %s inet6 %s/%d", + IFCONFIG_PATH, + actual, + ifconfig_ipv6_local, + tt->netbits_ipv6 + ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, S_FATAL, "FreeBSD ifconfig inet6 failed"); + } + #elif defined (WIN32) { /* @@ -959,6 +1159,34 @@ do_ifconfig (struct tuntap *tt, tt->did_ifconfig = true; } + /* IPv6 always uses "netsh" interface */ + if ( do_ipv6 ) + { + char * saved_actual; + + if (!strcmp (actual, "NULL")) + msg (M_FATAL, "Error: When using --tun-ipv6, if you have more than one TAP-Win32 adapter, you must also specify --dev-node"); + + /* example: netsh interface ipv6 add address MyTap 2001:608:8003::d */ + argv_printf (&argv, + "%s%sc interface ipv6 add address %s %s", + get_win_sys_path(), + NETSH_PATH_SUFFIX, + actual, + ifconfig_ipv6_local ); + + netsh_command (&argv, 4); + + /* explicit route needed */ + /* on windows, OpenVPN does ifconfig first, open_tun later, so + * tt->actual_name might not yet be initialized, but routing code + * needs to know interface name - point to "actual", restore later + */ + saved_actual = tt->actual_name; + tt->actual_name = (char*) actual; + add_route_connected_v6_net(tt, es); + tt->actual_name = saved_actual; + } #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 @@ -991,14 +1219,16 @@ open_null (struct tuntap *tt) #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, + 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->ipv6 && ! ipv6_explicitly_supported ) + msg (M_WARN, "NOTE: explicit support for IPv6 tun devices is not provided for this OS"); if (tt->type == DEV_TYPE_NULL) { @@ -1094,16 +1324,16 @@ close_tun_generic (struct tuntap *tt) #if !PEDANTIC void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, 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. + /* warn if a very old linux version is used & --tun-ipv6 set */ - ipv6_support (ipv6, LINUX_IPV6, tt); +#if LINUX_IPV6 == 0 + if ( tt->ipv6 ) + msg (M_WARN, "NOTE: explicit support for IPv6 tun devices is not provided for this OS"); +#endif /* * We handle --dev null specially, we do not open /dev/null for this. @@ -1215,13 +1445,13 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 close (tt->fd); tt->fd = -1; } - open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); + open_tun_generic (dev, dev_type, dev_node, false, true, tt); } #else void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { ASSERT (0); } @@ -1231,9 +1461,9 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 #else void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); + open_tun_generic (dev, dev_type, dev_node, false, true, tt); } #endif /* HAVE_LINUX_IF_TUN_H */ @@ -1253,7 +1483,7 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 #endif void -tuncfg (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, int persist_mode, const char *username, const char *groupname, const struct tuntap_options *options) +tuncfg (const char *dev, const char *dev_type, const char *dev_node, int persist_mode, const char *username, const char *groupname, const struct tuntap_options *options) { struct tuntap *tt; @@ -1261,7 +1491,7 @@ tuncfg (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, clear_tuntap (tt); tt->type = dev_type_enum (dev, dev_type); tt->options = *options; - open_tun (dev, dev_type, dev_node, ipv6, tt); + open_tun (dev, dev_type, dev_node, tt); if (ioctl (tt->fd, TUNSETPERSIST, persist_mode) < 0) msg (M_ERR, "Cannot ioctl TUNSETPERSIST(%d) %s", persist_mode, dev); if (username != NULL) @@ -1404,7 +1634,7 @@ read_tun (struct tuntap* tt, uint8_t *buf, int len) #endif void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { int if_fd, ip_muxid, arp_muxid, arp_fd, ppa = -1; struct lifreq ifr; @@ -1415,7 +1645,10 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 bool is_tun; struct strioctl strioc_if, strioc_ppa; - ipv6_support (ipv6, true, tt); + /* improved generic TUN/TAP driver from + * http://www.whiteboard.ne.jp/~admin2/tuntap/ + * has IPv6 support + */ memset(&ifr, 0x0, sizeof(ifr)); if (tt->type == DEV_TYPE_NULL) @@ -1570,6 +1803,18 @@ solaris_close_tun (struct tuntap *tt) { if (tt) { + /* IPv6 interfaces need to be 'manually' de-configured */ + if ( tt->ipv6 && tt->did_ifconfig_ipv6_setup ) + { + struct argv argv; + argv_init (&argv); + argv_printf( &argv, "%s %s inet6 unplumb", + IFCONFIG_PATH, tt->actual_name ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, NULL, 0, "Solaris ifconfig inet6 unplumb failed"); + argv_reset (&argv); + } + if (tt->ip_fd >= 0) { struct lifreq ifr; @@ -1622,11 +1867,20 @@ close_tun (struct tuntap *tt) } static void -solaris_error_close (struct tuntap *tt, const struct env_set *es, const char *actual) +solaris_error_close (struct tuntap *tt, const struct env_set *es, + const char *actual, bool unplumb_inet6 ) { struct argv argv; argv_init (&argv); + if (unplumb_inet6) + { + argv_printf( &argv, "%s %s inet6 unplumb", + IFCONFIG_PATH, actual ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, es, 0, "Solaris ifconfig inet6 unplumb failed"); + } + argv_printf (&argv, "%s %s unplumb", IFCONFIG_PATH, @@ -1683,9 +1937,9 @@ read_tun (struct tuntap* tt, uint8_t *buf, int len) */ void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + open_tun_generic (dev, dev_type, dev_node, true, true, tt); /* Enable multicast on the interface */ if (tt->fd >= 0) @@ -1708,12 +1962,31 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 } } +/* the current way OpenVPN handles tun devices on OpenBSD leads to + * lingering tunX interfaces after close -> for a full cleanup, they + * need to be explicitely destroyed + */ + void close_tun (struct tuntap* tt) { if (tt) { + struct gc_arena gc = gc_new (); + struct argv argv; + + /* setup command, close tun dev (clears tt->actual_name!), run command + */ + + argv_init (&argv); + argv_printf (&argv, "%s %s destroy", + IFCONFIG_PATH, tt->actual_name); + close_tun_generic (tt); + + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, NULL, 0, "OpenBSD 'destroy tun interface' failed (non-critical)"); + free (tt); } } @@ -1776,33 +2049,51 @@ read_tun (struct tuntap* tt, uint8_t *buf, int 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. + * NetBSD before 4.0 does not support IPv6 on tun out of the box, + * but there exists a patch (sys/net/if_tun.c, 1.79->1.80, see PR 32944). + * + * NetBSD 4.0 and up do, but we need to put the tun interface into + * "multi_af" mode, which will prepend the address family to all packets + * (same as OpenBSD and FreeBSD). If this is not enabled, the kernel + * silently drops all IPv6 packets on output and gets confused on input. + * + * On earlier versions, multi_af is not available at all, so we have + * two different NetBSD code variants here :-( + * */ void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); +#ifdef NETBSD_MULTI_AF + open_tun_generic (dev, dev_type, dev_node, true, true, tt); +#else + open_tun_generic (dev, dev_type, dev_node, false, true, tt); +#endif + 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 */ + +#ifdef NETBSD_MULTI_AF + i = 1; + if (ioctl (tt->fd, TUNSIFHEAD, &i) < 0) /* multi-af mode on */ + { + msg (M_WARN | M_ERRNO, "ioctl(TUNSIFHEAD): %s", strerror(errno)); + } +#endif } } void close_tun (struct tuntap *tt) { + /* TODO: we really should cleanup non-persistant tunX with + * "ifconfig tunX destroy" here... + */ if (tt) { close_tun_generic (tt); @@ -1810,6 +2101,65 @@ close_tun (struct tuntap *tt) } } +#ifdef NETBSD_MULTI_AF + +static inline int +netbsd_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 openvpn_iphdr *iph; + + iph = (struct openvpn_iphdr *) buf; + + if (tt->ipv6 && OPENVPN_IPH_GET_VER(iph->version_len) == 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 netbsd_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 netbsd_modify_read_write_return (readv (tt->fd, iv, 2)); + } + else + return read (tt->fd, buf, len); +} + +#else /* not NETBSD_MULTI_AF -> older code, IPv4 only */ + int write_tun (struct tuntap* tt, uint8_t *buf, int len) { @@ -1821,6 +2171,7 @@ read_tun (struct tuntap* tt, uint8_t *buf, int len) { return read (tt->fd, buf, len); } +#endif /* NETBSD_MULTI_AF */ #elif defined(TARGET_FREEBSD) @@ -1834,9 +2185,9 @@ freebsd_modify_read_write_return (int len) } void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + open_tun_generic (dev, dev_type, dev_node, true, true, tt); if (tt->fd >= 0 && tt->type == DEV_TYPE_TUN) { @@ -1922,9 +2273,9 @@ dragonfly_modify_read_write_return (int len) } void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, true, true, tt); + open_tun_generic (dev, dev_type, dev_node, true, true, tt); if (tt->fd >= 0) { @@ -1993,6 +2344,61 @@ read_tun (struct tuntap* tt, uint8_t *buf, int len) return read (tt->fd, buf, len); } +#elif defined(TARGET_DARWIN) + +/* Darwin (MacOS X) is mostly "just use the generic stuff", but there + * is always one caveat...: + * + * If IPv6 is configured, and the tun device is closed, the IPv6 address + * configured to the tun interface changes to a lingering /128 route + * pointing to lo0. Need to unconfigure... (observed on 10.5) + */ + +void +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) +{ + open_tun_generic (dev, dev_type, dev_node, false, true, tt); +} + +void +close_tun (struct tuntap* tt) +{ + if (tt) + { + struct gc_arena gc = gc_new (); + struct argv argv; + argv_init (&argv); + + if ( tt->ipv6 && tt->did_ifconfig_ipv6_setup ) + { + const char * ifconfig_ipv6_local = + print_in6_addr (tt->local_ipv6, 0, &gc); + + argv_printf (&argv, "%s delete -inet6 %s", + ROUTE_PATH, ifconfig_ipv6_local ); + argv_msg (M_INFO, &argv); + openvpn_execve_check (&argv, NULL, 0, "MacOS X 'remove inet6 route' failed (non-critical)"); + } + + close_tun_generic (tt); + free (tt); + argv_reset (&argv); + gc_free (&gc); + } +} + +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(WIN32) int @@ -3978,7 +4384,7 @@ fork_register_dns_action (struct tuntap *tt) } void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { struct gc_arena gc = gc_new (); char device_path[256]; @@ -3989,7 +4395,7 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 /*netcmd_semaphore_lock ();*/ - ipv6_support (ipv6, false, tt); + msg( M_INFO, "open_tun, tt->ipv6=%d", tt->ipv6 ); if (tt->type == DEV_TYPE_NULL) { @@ -4111,6 +4517,16 @@ open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6 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); + + /* usage of numeric constants is ugly, but this is really tied to + * *this* version of the driver + */ + if ( tt->ipv6 && tt->type == DEV_TYPE_TUN && + info[0] == 9 && info[1] < 8) + { + msg( M_INFO, "WARNING: Tap-Win32 driver version %d.%d does not support IPv6 in TUN mode. IPv6 will be disabled. Upgrade to Tap-Win32 9.8 (2.2-beta3 release or later) or use TAP mode to get IPv6", (int) info[0], (int) info[1] ); + tt->ipv6 = false; + } } /* get driver MTU */ @@ -4435,6 +4851,12 @@ close_tun (struct tuntap *tt) if (tt) { + if ( tt->ipv6 && tt->did_ifconfig_ipv6_setup ) + { + /* netsh interface ipv6 delete address \"%s\" %s */ + const char * ifconfig_ipv6_local = print_in6_addr (tt->local_ipv6, 0, &gc); + msg( M_WARN, "TODO: remove IPv6 address %s", ifconfig_ipv6_local ); + } #if 1 if (tt->ipapi_context_defined) { @@ -4538,9 +4960,9 @@ ipset2ascii_all (struct gc_arena *gc) #else /* generic */ void -open_tun (const char *dev, const char *dev_type, const char *dev_node, bool ipv6, struct tuntap *tt) +open_tun (const char *dev, const char *dev_type, const char *dev_node, struct tuntap *tt) { - open_tun_generic (dev, dev_type, dev_node, ipv6, false, true, tt); + open_tun_generic (dev, dev_type, dev_node, false, true, tt); } void @@ -130,6 +130,7 @@ struct tuntap int topology; /* one of the TOP_x values */ bool did_ifconfig_setup; + bool did_ifconfig_ipv6_setup; bool did_ifconfig; bool ipv6; @@ -146,6 +147,10 @@ struct tuntap in_addr_t remote_netmask; in_addr_t broadcast; + struct in6_addr local_ipv6; + struct in6_addr remote_ipv6; + int netbits_ipv6; + #ifdef WIN32 HANDLE hand; struct overlapped_io reads; @@ -197,7 +202,7 @@ tuntap_defined (const struct tuntap *tt) void clear_tuntap (struct tuntap *tuntap); void open_tun (const char *dev, const char *dev_type, const char *dev_node, - bool ipv6, struct tuntap *tt); + struct tuntap *tt); void close_tun (struct tuntap *tt); @@ -206,7 +211,7 @@ int write_tun (struct tuntap* tt, uint8_t *buf, int len); int read_tun (struct tuntap* tt, uint8_t *buf, int len); void tuncfg (const char *dev, const char *dev_type, const char *dev_node, - bool ipv6, int persist_mode, const char *username, + int persist_mode, const char *username, const char *groupname, const struct tuntap_options *options); const char *guess_tuntap_dev (const char *dev, @@ -219,6 +224,8 @@ struct tuntap *init_tun (const char *dev, /* --dev option */ int topology, /* one of the TOP_x values */ const char *ifconfig_local_parm, /* --ifconfig parm 1 */ const char *ifconfig_remote_netmask_parm, /* --ifconfig parm 2 */ + const char *ifconfig_ipv6_local_parm, /* --ifconfig parm 1 / IPv6 */ + const char *ifconfig_ipv6_remote_parm, /* --ifconfig parm 2 / IPv6 */ in_addr_t local_public, in_addr_t remote_public, const bool strict_warn, @@ -874,16 +874,21 @@ win_safe_filename (const char *fn) static char * env_block (const struct env_set *es) { + char * force_path = "PATH=C:\\Windows\\System32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem"; + if (es) { struct env_item *e; char *ret; char *p; size_t nchars = 1; + bool path_seen = false; for (e = es->list; e != NULL; e = e->next) nchars += strlen (e->string) + 1; + nchars += strlen(force_path)+1; + ret = (char *) malloc (nchars); check_malloc_return (ret); @@ -895,7 +900,18 @@ env_block (const struct env_set *es) strcpy (p, e->string); p += strlen (e->string) + 1; } + if ( strncmp(e->string, "PATH=", 5 ) == 0 ) + path_seen = true; + } + + /* make sure PATH is set */ + if ( !path_seen ) + { + msg( M_INFO, "env_block: add %s", force_path ); + strcpy( p, force_path ); + p += strlen(force_path) + 1; } + *p = '\0'; return ret; } @@ -272,6 +272,8 @@ char *get_win_sys_path (void); /* call self in a subprocess */ void fork_to_self (const char *cmdline); +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); +int inet_pton(int af, const char *src, void *st); /* Find temporary directory */ const char *win_get_tempdir(); |