Description: IPv6 payload support
Author: Gert Döring
URL: http://www.greenie.net/ipv6/openvpn.html
Index: openvpn-2.2.1/ChangeLog.IPv6
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ openvpn-2.2.1/ChangeLog.IPv6	2011-12-13 12:24:54.608739565 +0100
@@ -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
+
Index: openvpn-2.2.1/README.IPv6
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ openvpn-2.2.1/README.IPv6	2011-12-13 12:24:54.608739565 +0100
@@ -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
Index: openvpn-2.2.1/TODO.IPv6
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ openvpn-2.2.1/TODO.IPv6	2011-12-13 12:24:54.609739553 +0100
@@ -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)
Index: openvpn-2.2.1/forward.c
===================================================================
--- openvpn-2.2.1.orig/forward.c	2011-06-24 08:13:38.000000000 +0200
+++ openvpn-2.2.1/forward.c	2011-12-13 12:24:54.611739529 +0100
@@ -262,7 +262,8 @@
 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);
Index: openvpn-2.2.1/helper.c
===================================================================
--- openvpn-2.2.1.orig/helper.c	2011-06-24 08:13:38.000000000 +0200
+++ openvpn-2.2.1/helper.c	2011-12-13 12:24:54.612739516 +0100
@@ -142,6 +142,55 @@
 
 #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:
Index: openvpn-2.2.1/init.c
===================================================================
--- openvpn-2.2.1.orig/init.c	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/init.c	2011-12-13 12:24:54.615739477 +0100
@@ -843,7 +843,7 @@
 	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 @@
 {
   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 @@
     }
 }
 
+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.
  */
@@ -1177,12 +1218,13 @@
 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))
     {
@@ -1239,11 +1281,16 @@
 			   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);
@@ -1275,6 +1322,8 @@
       /* 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
@@ -1291,7 +1340,7 @@
 
       /* 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 @@
 
       /* 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?
@@ -1394,8 +1444,9 @@
 #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)
Index: openvpn-2.2.1/init.h
===================================================================
--- openvpn-2.2.1.orig/init.h	2011-06-24 08:13:38.000000000 +0200
+++ openvpn-2.2.1/init.h	2011-12-13 12:24:54.615739477 +0100
@@ -63,6 +63,7 @@
 
 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);
Index: openvpn-2.2.1/misc.c
===================================================================
--- openvpn-2.2.1.orig/misc.c	2011-06-24 08:13:38.000000000 +0200
+++ openvpn-2.2.1/misc.c	2011-12-13 12:24:54.617739452 +0100
@@ -1001,7 +1001,9 @@
 	{
 	  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);
Index: openvpn-2.2.1/mroute.c
===================================================================
--- openvpn-2.2.1.orig/mroute.c	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/mroute.c	2011-12-13 12:24:54.618739440 +0100
@@ -88,12 +88,33 @@
     }
 }
 
+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 @@
 	    }
 	  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 @@
  * 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 @@
 	  }
 	  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
@@ -439,6 +508,40 @@
 	mroute_helper_regenerate (mh);
     }
 }
+
+/* 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)
Index: openvpn-2.2.1/mroute.h
===================================================================
--- openvpn-2.2.1.orig/mroute.h	2011-06-24 08:13:38.000000000 +0200
+++ openvpn-2.2.1/mroute.h	2011-12-13 12:24:54.618739440 +0100
@@ -85,7 +85,7 @@
 /*
  * 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 @@
 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
Index: openvpn-2.2.1/multi.c
===================================================================
--- openvpn-2.2.1.orig/multi.c	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/multi.c	2011-12-13 12:24:54.621739404 +0100
@@ -316,25 +316,18 @@
    */
   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 @@
 		   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 @@
   }
 }
 
+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 @@
 {
   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_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 @@
       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 @@
 	  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 @@
 			    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 @@
 		       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);
Index: openvpn-2.2.1/openvpn.8
===================================================================
--- openvpn-2.2.1.orig/openvpn.8	2011-12-13 12:24:20.000000000 +0100
+++ openvpn-2.2.1/openvpn.8	2011-12-13 12:24:54.628739315 +0100
@@ -794,6 +794,8 @@
 .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
@@ -4949,6 +4951,57 @@
 .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.
Index: openvpn-2.2.1/openvpn.h
===================================================================
--- openvpn-2.2.1.orig/openvpn.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/openvpn.h	2011-12-13 12:24:54.629739303 +0100
@@ -165,6 +165,9 @@
   /* 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 @@
   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
Index: openvpn-2.2.1/options.c
===================================================================
--- openvpn-2.2.1.orig/options.c	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/options.c	2011-12-13 12:24:54.635739227 +0100
@@ -85,6 +85,7 @@
 #ifdef USE_PF_INET6
   " [PF_INET6]"
 #endif
+  " [IPv6 payload 20110424-2 (2.2RC2)]"
   " built on " __DATE__
 ;
 
@@ -181,6 +182,8 @@
   "                  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 @@
   "                  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 @@
   "\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 @@
   "--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 @@
   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 @@
 #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 @@
   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_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 @@
   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 @@
     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_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 @@
       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 @@
 	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 @@
        */
       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 @@
 		     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,
@@ -3850,6 +3984,30 @@
 	  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);
@@ -4650,6 +4808,26 @@
 	}
       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;
@@ -4861,6 +5039,33 @@
 	    }
 	}
     }
+  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;
@@ -4945,6 +5150,28 @@
       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;
@@ -5140,6 +5367,11 @@
 	}
       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;
@@ -5178,6 +5410,43 @@
 	  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);
Index: openvpn-2.2.1/options.h
===================================================================
--- openvpn-2.2.1.orig/options.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/options.h	2011-12-13 12:24:54.636739214 +0100
@@ -205,6 +205,9 @@
   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 @@
   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 */
@@ -363,6 +367,9 @@
   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)
@@ -384,6 +391,11 @@
   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;
@@ -395,12 +407,17 @@
   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;
@@ -723,6 +740,10 @@
 			    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
  */
Index: openvpn-2.2.1/pool.c
===================================================================
--- openvpn-2.2.1.orig/pool.c	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/pool.c	2011-12-13 12:24:54.637739202 +0100
@@ -132,7 +132,10 @@
 }
 
 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 @@
       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_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 @@
 	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 @@
   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 @@
 	  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 @@
 	      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 @@
 		  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 @@
 #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 @@
 #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",
Index: openvpn-2.2.1/pool.h
===================================================================
--- openvpn-2.2.1.orig/pool.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/pool.h	2011-12-13 12:24:54.637739202 +0100
@@ -52,6 +52,9 @@
   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 @@
 
 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);
 
Index: openvpn-2.2.1/proto.h
===================================================================
--- openvpn-2.2.1.orig/proto.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/proto.h	2011-12-13 12:24:54.638739190 +0100
@@ -108,6 +108,21 @@
 };
 
 /*
+ * 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 {
Index: openvpn-2.2.1/push.c
===================================================================
--- openvpn-2.2.1.orig/push.c	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/push.c	2011-12-13 12:24:54.638739190 +0100
@@ -189,8 +189,26 @@
   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)
Index: openvpn-2.2.1/route.c
===================================================================
--- openvpn-2.2.1.orig/route.c	2011-12-13 12:24:33.000000000 +0100
+++ openvpn-2.2.1/route.c	2011-12-13 12:24:54.641739154 +0100
@@ -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 @@
   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 @@
   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 @@
   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 @@
 }
 
 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 @@
 }
 
 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 @@
   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 @@
 }
 
 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 @@
 	}
       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 @@
 	}
       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_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 @@
   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)
 {
@@ -1171,6 +1580,142 @@
 #endif
 
   argv_reset (&argv);
+  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);
 }
 
Index: openvpn-2.2.1/route.h
===================================================================
--- openvpn-2.2.1.orig/route.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/route.h	2011-12-13 12:24:54.642739141 +0100
@@ -92,6 +92,19 @@
   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 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 @@
   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 @@
 			       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 @@
 		      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);
 
Index: openvpn-2.2.1/socket.c
===================================================================
--- openvpn-2.2.1.orig/socket.c	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/socket.c	2011-12-13 12:24:54.645739102 +0100
@@ -543,6 +543,24 @@
   }
 }
 
+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 @@
   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 @@
 
 #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)
 {
Index: openvpn-2.2.1/socket.h
===================================================================
--- openvpn-2.2.1.orig/socket.h	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/socket.h	2011-12-13 12:24:54.646739089 +0100
@@ -368,6 +368,8 @@
 #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 @@
 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);
 
Index: openvpn-2.2.1/tun.c
===================================================================
--- openvpn-2.2.1.orig/tun.c	2011-12-13 12:23:07.394079932 +0100
+++ openvpn-2.2.1/tun.c	2011-12-13 12:41:30.078294479 +0100
@@ -56,13 +56,14 @@
 			    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 @@
   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 @@
 	  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 @@
 
       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 @@
 #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 @@
       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 @@
       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 @@
 		  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 @@
 			  );
       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 @@
 
 	  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 @@
 
       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 @@
 			  );
       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 @@
 			  );
       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 @@
 	  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 @@
           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 @@
 	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 @@
 #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 @@
 #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.
@@ -1212,7 +1442,7 @@
 #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);
 }
@@ -1222,9 +1452,9 @@
 #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 */
@@ -1244,7 +1474,7 @@
 #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;
 
@@ -1252,7 +1482,7 @@
   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)
@@ -1395,7 +1625,7 @@
 #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;
@@ -1406,7 +1636,10 @@
   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)
@@ -1561,6 +1794,18 @@
 {
   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;
@@ -1613,11 +1858,20 @@
 }
 
 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,
@@ -1674,9 +1928,9 @@
  */
 
 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)
@@ -1699,12 +1953,31 @@
     }
 }
 
+/* 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);
     }
 }
@@ -1767,33 +2040,51 @@
 #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);
@@ -1801,6 +2092,65 @@
     }
 }
 
+#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)
 {
@@ -1812,6 +2162,7 @@
 {
     return read (tt->fd, buf, len);
 }
+#endif	/* NETBSD_MULTI_AF */
 
 #elif defined(TARGET_FREEBSD)
 
@@ -1825,9 +2176,9 @@
 }
 
 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)
     {
@@ -1913,9 +2264,9 @@
 }
 
 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)
     {
@@ -1984,6 +2335,61 @@
     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
@@ -3969,7 +4375,7 @@
 }
 
 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];
@@ -3980,7 +4386,7 @@
 
   /*netcmd_semaphore_lock ();*/
 
-  ipv6_support (ipv6, false, tt);
+  msg( M_INFO, "open_tun, tt->ipv6=%d", tt->ipv6 );
 
   if (tt->type == DEV_TYPE_NULL)
     {
@@ -4102,6 +4508,16 @@
       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 */
@@ -4426,6 +4842,12 @@
 
   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)
 	{
@@ -4529,9 +4951,9 @@
 #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
Index: openvpn-2.2.1/tun.h
===================================================================
--- openvpn-2.2.1.orig/tun.h	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/tun.h	2011-12-13 12:24:54.653739003 +0100
@@ -130,6 +130,7 @@
   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 @@
   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 @@
 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 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 @@
 			 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,
Index: openvpn-2.2.1/win32.c
===================================================================
--- openvpn-2.2.1.orig/win32.c	2011-06-24 08:13:39.000000000 +0200
+++ openvpn-2.2.1/win32.c	2011-12-13 12:24:54.654738990 +0100
@@ -874,16 +874,21 @@
 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 @@
 	      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;
     }
Index: openvpn-2.2.1/win32.h
===================================================================
--- openvpn-2.2.1.orig/win32.h	2011-12-13 12:23:07.000000000 +0100
+++ openvpn-2.2.1/win32.h	2011-12-13 12:24:54.654738990 +0100
@@ -272,6 +272,8 @@
 
 /* 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();
