--- acinclude.m4
+++ acinclude.m4
@@ -123,5 +123,9 @@
       AC_DEFINE_UNQUOTED(socklen_t, $curl_cv_socklen_t_equiv,
 			[type to use in place of socklen_t if not defined])],
       [#include <sys/types.h>
-#include <sys/socket.h>])
+#ifdef WIN32
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#endif])
 ])
--- aclocal.m4
+++ aclocal.m4
@@ -13,8 +13,8 @@
 
 m4_ifndef([AC_AUTOCONF_VERSION],
   [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
-m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.66],,
-[m4_warning([this file was generated for autoconf 2.66.
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.67],,
+[m4_warning([this file was generated for autoconf 2.67.
 You have another version of autoconf.  It may work, but is not guaranteed to.
 If you have problems, you may need to regenerate the build system entirely.
 To do so, use the procedure documented by the package, typically `autoreconf'.])])
--- buffer.c
+++ buffer.c
@@ -214,6 +214,23 @@
   return ret;
 }
 
+bool
+buf_puts(struct buffer *buf, const char *str)
+{
+  int ret = false;
+  uint8_t *ptr = BEND (buf);
+  int cap = buf_forward_capacity (buf);
+  if (cap > 0)
+    {
+      strncpynt ((char *)ptr,str, cap);
+      *(buf->data + buf->capacity - 1) = 0; /* windows vsnprintf needs this */
+      buf->len += (int) strlen ((char *)ptr);
+      ret = true;
+    }
+  return ret;
+}
+ 
+
 /*
  * This is necessary due to certain buggy implementations of snprintf,
  * that don't guarantee null termination for size > 0.
--- buffer.h
+++ buffer.h
@@ -277,6 +277,11 @@
     ;
 
 /*
+ * puts append to a buffer with overflow check
+ */
+bool buf_puts (struct buffer *buf, const char *str);
+
+/*
  * Like snprintf but guarantees null termination for size > 0
  */
 int openvpn_snprintf(char *str, size_t size, const char *format, ...)
--- config.h.in
+++ config.h.in
@@ -531,6 +531,9 @@
 /* Use LZO compression library */
 #undef USE_LZO
 
+/* struct sockaddr_in6 is needed for IPv6 peer support */
+#undef USE_PF_INET6
+
 /* Enable PKCS11 capability */
 #undef USE_PKCS11
 
--- configure
+++ configure
@@ -715,6 +715,7 @@
 enable_http
 enable_fragment
 enable_multihome
+enable_ipv6
 enable_port_share
 enable_debug
 enable_small
@@ -1383,6 +1384,7 @@
   --disable-http          Disable HTTP proxy support
   --disable-fragment      Disable internal fragmentation support (--fragment)
   --disable-multihome     Disable multi-homed UDP server support (--multihome)
+  --disable-ipv6          Disable UDP/IPv6 support
   --disable-port-share    Disable TCP server port-share support (--port-share)
   --disable-debug         Disable debugging support (disable gremlin and verb 7+ messages)
   --enable-small          Enable smaller executable size (disable OCC, usage message, and verb 4 parm list)
@@ -3184,6 +3186,15 @@
 fi
 
 
+# Check whether --enable-ipv6 was given.
+if test "${enable_ipv6+set}" = set; then :
+  enableval=$enable_ipv6; PF_INET6="$enableval"
+else
+  PF_INET6="yes"
+
+fi
+
+
 # Check whether --enable-port-share was given.
 if test "${enable_port_share+set}" = set; then :
   enableval=$enable_port_share; PORT_SHARE="$enableval"
@@ -5518,7 +5529,11 @@
 
 
    ac_fn_c_check_type "$LINENO" "socklen_t" "ac_cv_type_socklen_t" "#include <sys/types.h>
+#ifdef WIN32
+#include <ws2tcpip.h>
+#else
 #include <sys/socket.h>
+#endif
 "
 if test "x$ac_cv_type_socklen_t" = x""yes; then :
 
@@ -6861,6 +6876,19 @@
 
 LDFLAGS="$OLDLDFLAGS"
 
+if test "$PF_INET6" = "yes"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for struct sockaddr_in6 for IPv6 support..." >&5
+$as_echo "$as_me: checking for struct sockaddr_in6 for IPv6 support..." >&6;}
+  ac_fn_c_check_type "$LINENO" "struct sockaddr_in6" "ac_cv_type_struct_sockaddr_in6" "#include \"syshead.h\"
+"
+if test "x$ac_cv_type_struct_sockaddr_in6" = x""yes; then :
+
+$as_echo "#define USE_PF_INET6 1" >>confdefs.h
+
+fi
+
+fi
+
 
 if test "$MEMCHECK" = "valgrind"; then
    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for valgrind tool and Header files..." >&5
--- configure.ac
+++ configure.ac
@@ -146,6 +146,12 @@
    [MULTIHOME="yes"]
 )
 
+AC_ARG_ENABLE(ipv6,
+   [  --disable-ipv6          Disable UDP/IPv6 support],
+   [PF_INET6="$enableval"],
+   [PF_INET6="yes"]
+)
+
 AC_ARG_ENABLE(port-share,
    [  --disable-port-share    Disable TCP server port-share support (--port-share)],
    [PORT_SHARE="$enableval"],
@@ -566,6 +572,16 @@
 AC_CHECK_FUNC(epoll_create, AC_DEFINE(HAVE_EPOLL_CREATE, 1, [epoll_create function is defined]))
 LDFLAGS="$OLDLDFLAGS"
 
+dnl ipv6 support
+if test "$PF_INET6" = "yes"; then
+  AC_CHECKING([for struct sockaddr_in6 for IPv6 support])
+  AC_CHECK_TYPE(
+      [struct sockaddr_in6],
+      [AC_DEFINE(USE_PF_INET6, 1, [struct sockaddr_in6 is needed for IPv6 peer support])],
+      [],
+      [#include "syshead.h"])
+fi
+
 dnl
 dnl check for valgrind tool
 dnl
--- init.c
+++ init.c
@@ -96,7 +96,7 @@
    */
   if (options->pull
       && options->ping_rec_timeout_action == PING_UNDEF
-      && options->ce.proto == PROTO_UDPv4)
+      && proto_is_dgram(options->ce.proto))
     {
       options->ping_rec_timeout = PRE_PULL_INITIAL_PING_RESTART;
       options->ping_rec_timeout_action = PING_RESTART;
@@ -1150,7 +1150,12 @@
       const char *detail = "SUCCESS";
       if (c->c1.tuntap)
 	tun_local = c->c1.tuntap->local;
-      tun_remote = htonl (c->c1.link_socket_addr.actual.dest.sa.sin_addr.s_addr);
+      /* TODO(jjo): for ipv6 this will convert some 32bits in the ipv6 addr
+       *            to a meaningless ipv4 address.
+       *            In any case, is somewhat inconsistent to send local tunnel
+       *            addr with remote _endpoint_ addr (?)
+       */
+      tun_remote = htonl (c->c1.link_socket_addr.actual.dest.addr.in4.sin_addr.s_addr);
       if (flags & ISC_ERRORS)
 	detail = "ERROR";
       management_set_state (management,
@@ -1566,7 +1571,7 @@
 #ifdef ENABLE_OCC
   if (found & OPT_P_EXPLICIT_NOTIFY)
     {
-      if (c->options.ce.proto != PROTO_UDPv4 && c->options.explicit_exit_notification)
+      if (!proto_is_udp(c->options.ce.proto) && c->options.explicit_exit_notification)
 	{
 	  msg (D_PUSH, "OPTIONS IMPORT: --explicit-exit-notify can only be used with --proto udp");
 	  c->options.explicit_exit_notification = 0;
@@ -1661,13 +1666,22 @@
   switch (c->options.ce.proto)
     {
     case PROTO_UDPv4:
+#ifdef USE_PF_INET6
+    case PROTO_UDPv6:
+#endif
       if (proxy)
 	sec = c->options.ce.connect_retry_seconds;
       break;
     case PROTO_TCPv4_SERVER:
+#ifdef USE_PF_INET6
+    case PROTO_TCPv6_SERVER:
+#endif
       sec = 1;
       break;
     case PROTO_TCPv4_CLIENT:
+#ifdef USE_PF_INET6
+    case PROTO_TCPv6_CLIENT:
+#endif
       sec = c->options.ce.connect_retry_seconds;
       break;
     }
@@ -2807,7 +2821,7 @@
 #ifdef WIN32
       msg (M_INFO, "NOTE: --fast-io is disabled since we are running on Windows");
 #else
-      if (c->options.ce.proto != PROTO_UDPv4)
+      if (!proto_is_udp(c->options.ce.proto))
 	msg (M_INFO, "NOTE: --fast-io is disabled since we are not using UDP");
       else
 	{
@@ -3083,7 +3097,11 @@
   /* link_socket_mode allows CM_CHILD_TCP
      instances to inherit acceptable fds
      from a top-level parent */
-  if (c->options.ce.proto == PROTO_TCPv4_SERVER)
+  if (c->options.ce.proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+      || c->options.ce.proto == PROTO_TCPv6_SERVER
+#endif
+     )
     {
       if (c->mode == CM_TOP)
 	link_socket_mode = LS_MODE_TCP_LISTEN;
@@ -3358,17 +3376,8 @@
 {
   CLEAR (*dest);
 
-  switch (src->options.ce.proto)
-    {
-    case PROTO_UDPv4:
-      dest->mode = CM_CHILD_UDP;
-      break;
-    case PROTO_TCPv4_SERVER:
-      dest->mode = CM_CHILD_TCP;
-      break;
-    default:
-      ASSERT (0);
-    }
+  /* proto_is_dgram will ASSERT(0) if proto is invalid */
+  dest->mode = proto_is_dgram(src->options.ce.proto)? CM_CHILD_UDP : CM_CHILD_TCP;
 
   dest->gc = gc_new ();
 
@@ -3474,7 +3483,7 @@
   dest->c2.es_owned = false;
 
   dest->c2.event_set = NULL;
-  if (src->options.ce.proto == PROTO_UDPv4)
+  if (proto_is_dgram(src->options.ce.proto))
     do_event_set_init (dest, false);
 }
 
--- manage.c
+++ manage.c
@@ -1957,9 +1957,9 @@
 	  /*
 	   * Initialize socket address
 	   */
-	  ms->local.sa.sin_family = AF_INET;
-	  ms->local.sa.sin_addr.s_addr = 0;
-	  ms->local.sa.sin_port = htons (port);
+	  ms->local.addr.in4.sin_family = AF_INET;
+	  ms->local.addr.in4.sin_addr.s_addr = 0;
+	  ms->local.addr.in4.sin_port = htons (port);
 
 	  /*
 	   * Run management over tunnel, or
@@ -1971,7 +1971,7 @@
 	    }
 	  else
 	    {
-	      ms->local.sa.sin_addr.s_addr = getaddr
+	      ms->local.addr.in4.sin_addr.s_addr = getaddr
 		(GETADDR_RESOLVE|GETADDR_WARN_ON_SIGNAL|GETADDR_FATAL, addr, 0, NULL, NULL);
 	    }
 	}
@@ -2427,7 +2427,7 @@
       && man->connection.state == MS_INITIAL)
     {
       /* listen on our local TUN/TAP IP address */
-      man->settings.local.sa.sin_addr.s_addr = htonl (tun_local_ip);
+      man->settings.local.addr.in4.sin_addr.s_addr = htonl (tun_local_ip);
       man_connection_init (man);
     }
 
--- mroute.c
+++ mroute.c
@@ -231,25 +231,47 @@
 				      const struct openvpn_sockaddr *osaddr,
 				      bool use_port)
 {
-  if (osaddr->sa.sin_family == AF_INET)
+  switch (osaddr->addr.sa.sa_family) 
+  {
+    case AF_INET:
     {
       if (use_port)
 	{
 	  addr->type = MR_ADDR_IPV4 | MR_WITH_PORT;
 	  addr->netbits = 0;
 	  addr->len = 6;
-	  memcpy (addr->addr, &osaddr->sa.sin_addr.s_addr, 4);
-	  memcpy (addr->addr + 4, &osaddr->sa.sin_port, 2);
+	  memcpy (addr->addr, &osaddr->addr.in4.sin_addr.s_addr, 4);
+	  memcpy (addr->addr + 4, &osaddr->addr.in4.sin_port, 2);
 	}
       else
 	{
 	  addr->type = MR_ADDR_IPV4;
 	  addr->netbits = 0;
 	  addr->len = 4;
-	  memcpy (addr->addr, &osaddr->sa.sin_addr.s_addr, 4);
+	  memcpy (addr->addr, &osaddr->addr.in4.sin_addr.s_addr, 4);
 	}
       return true;
     }
+#ifdef USE_PF_INET6
+    case AF_INET6:
+      if (use_port)
+	{
+	  addr->type = MR_ADDR_IPV6 | MR_WITH_PORT;
+	  addr->netbits = 0;
+	  addr->len = 18;
+	  memcpy (addr->addr, &osaddr->addr.in6.sin6_addr, 16);
+	  memcpy (addr->addr + 16, &osaddr->addr.in6.sin6_port, 2);
+	}
+      else
+	{
+	  addr->type = MR_ADDR_IPV6;
+	  addr->netbits = 0;
+	  addr->len = 16;
+	  memcpy (addr->addr, &osaddr->addr.in6.sin6_addr, 16);
+	}
+      return true;
+#endif
+  }
   return false;
 }
 
--- mtcp.c
+++ mtcp.c
@@ -150,6 +150,11 @@
   ASSERT (mi->context.c2.link_socket);
   ASSERT (mi->context.c2.link_socket->info.lsa);
   ASSERT (mi->context.c2.link_socket->mode == LS_MODE_TCP_ACCEPT_FROM);
+  ASSERT (mi->context.c2.link_socket->info.lsa->actual.dest.addr.sa.sa_family == AF_INET
+#ifdef USE_PF_INET6
+	  || mi->context.c2.link_socket->info.lsa->actual.dest.addr.sa.sa_family == AF_INET6
+#endif
+	  );
   if (!mroute_extract_openvpn_sockaddr (&mi->real, &mi->context.c2.link_socket->info.lsa->actual.dest, true))
     {
       msg (D_MULTI_ERRORS, "MULTI TCP: TCP client address is undefined");
--- multi.c
+++ multi.c
@@ -1058,8 +1058,8 @@
   struct mroute_addr addr;
 
   CLEAR (remote_si);
-  remote_si.sa.sin_family = AF_INET;
-  remote_si.sa.sin_addr.s_addr = htonl (a);
+  remote_si.addr.in4.sin_family = AF_INET;
+  remote_si.addr.in4.sin_addr.s_addr = htonl (a);
   ASSERT (mroute_extract_openvpn_sockaddr (&addr, &remote_si, false));
 
   if (netbits >= 0)
@@ -2496,9 +2496,9 @@
   int count = 0;
 
   CLEAR (saddr);
-  saddr.sa.sin_family = AF_INET;
-  saddr.sa.sin_addr.s_addr = htonl (addr);
-  saddr.sa.sin_port = htons (port);
+  saddr.addr.in4.sin_family = AF_INET;
+  saddr.addr.in4.sin_addr.s_addr = htonl (addr);
+  saddr.addr.in4.sin_port = htons (port);
   if (mroute_extract_openvpn_sockaddr (&maddr, &saddr, true))
     {
       hash_iterator_init (m->iter, &hi);
@@ -2675,16 +2675,24 @@
 {
   ASSERT (top->options.mode == MODE_SERVER);
 
-  switch (top->options.ce.proto) {
-  case PROTO_UDPv4:
-    tunnel_server_udp (top);
-    break;
-  case PROTO_TCPv4_SERVER:
-    tunnel_server_tcp (top);
-    break;
-  default:
-    ASSERT (0);
-  }
+#ifdef USE_PF_INET6
+  if (proto_is_dgram(top->options.ce.proto))
+    tunnel_server_udp(top);
+  else
+    tunnel_server_tcp(top);
+#else
+  switch (top->options.ce.proto)
+    {
+    case PROTO_UDPv4:
+      tunnel_server_udp (top);
+      break;
+    case PROTO_TCPv4_SERVER:
+      tunnel_server_tcp (top);
+      break;
+    default:
+      ASSERT (0);
+    }
+#endif
 }
 
 #else
--- occ.c
+++ occ.c
@@ -369,7 +369,7 @@
 	       c->c2.max_send_size_remote,
 	       c->c2.max_recv_size_local);
 	  if (!c->options.fragment
-	      && c->options.ce.proto == PROTO_UDPv4
+	      && (proto_is_dgram(c->options.ce.proto))
 	      && c->c2.max_send_size_local > TUN_MTU_MIN
 	      && (c->c2.max_recv_size_remote < c->c2.max_send_size_local
 		  || c->c2.max_recv_size_local < c->c2.max_send_size_remote))
--- openvpn.8
+++ openvpn.8
@@ -5463,13 +5463,16 @@
 script execution.
 .\"*********************************************************
 .TP
-.B trusted_ip
+.B trusted_ip (or trusted_ip6)
 Actual IP address of connecting client or peer which has been authenticated.
 Set prior to execution of
 .B \-\-ipchange, \-\-client-connect,
 and
 .B \-\-client-disconnect
 scripts.
+If using ipv6 endpoints (udp6, tcp6),
+.B trusted_ip6
+will be set instead.
 .\"*********************************************************
 .TP
 .B trusted_port
@@ -5481,7 +5484,7 @@
 scripts.
 .\"*********************************************************
 .TP
-.B untrusted_ip
+.B untrusted_ip (or untrusted_ip6)
 Actual IP address of connecting client or peer which has not been authenticated
 yet.  Sometimes used to
 .B nmap
@@ -5493,6 +5496,9 @@
 and
 .B \-\-auth-user-pass-verify
 scripts.
+If using ipv6 endpoints (udp6, tcp6),
+.B untrusted_ip6
+will be set instead.
 .\"*********************************************************
 .TP
 .B untrusted_port
--- options.c
+++ options.c
@@ -79,6 +79,12 @@
 #ifdef ENABLE_EUREPHIA
   " [eurephia]"
 #endif
+#if ENABLE_IP_PKTINFO
+  " [MH]"
+#endif
+#ifdef USE_PF_INET6
+  " [PF_INET6]"
+#endif
   " built on " __DATE__
 ;
 
@@ -101,6 +107,9 @@
   "--proto p       : Use protocol p for communicating with peer.\n"
   "                  p = udp (default), tcp-server, or tcp-client\n"
   "--proto-force p : only consider protocol p in list of connection profiles.\n"
+#ifdef USE_PF_INET6
+  "                  p = udp6, tcp6-server, or tcp6-client (ipv6)\n"
+#endif
   "--connect-retry n : For --proto tcp-client, number of seconds to wait\n"
   "                    between connection retries (default=%d).\n"
   "--connect-timeout n : For --proto tcp-client, connection timeout (in seconds).\n"
@@ -1707,11 +1716,27 @@
    * Sanity check on TCP mode options
    */
 
-  if (ce->connect_retry_defined && ce->proto != PROTO_TCPv4_CLIENT)
-    msg (M_USAGE, "--connect-retry doesn't make sense unless also used with --proto tcp-client");
-
-  if (ce->connect_timeout_defined && ce->proto != PROTO_TCPv4_CLIENT)
-    msg (M_USAGE, "--connect-timeout doesn't make sense unless also used with --proto tcp-client");
+  if (ce->connect_retry_defined && ce->proto != PROTO_TCPv4_CLIENT
+#ifdef USE_PF_INET6
+      && ce->proto != PROTO_TCPv6_CLIENT
+#endif
+      )
+    msg (M_USAGE, "--connect-retry doesn't make sense unless also used with --proto tcp-client"
+#ifdef USE_PF_INET6
+	 " or tcp6-client"
+#endif
+	 );
+
+  if (ce->connect_timeout_defined && ce->proto != PROTO_TCPv4_CLIENT
+#ifdef USE_PF_INET6
+      && ce->proto != PROTO_TCPv6_CLIENT
+#endif
+      )
+    msg (M_USAGE, "--connect-timeout doesn't make sense unless also used with --proto tcp-client"
+#ifdef USE_PF_INET6
+	 " or tcp6-client"
+#endif
+	 );
 
   /*
    * Sanity check on MTU parameters
@@ -1720,7 +1745,7 @@
     msg (M_USAGE, "only one of --tun-mtu or --link-mtu may be defined (note that --ifconfig implies --link-mtu %d)", LINK_MTU_DEFAULT);
 
 #ifdef ENABLE_OCC
-  if (ce->proto != PROTO_UDPv4 && options->mtu_test)
+  if (!proto_is_udp(ce->proto) && options->mtu_test)
     msg (M_USAGE, "--mtu-test only makes sense with --proto udp");
 #endif
 
@@ -1733,7 +1758,8 @@
    * Sanity check on --local, --remote, and --ifconfig
    */
 
-  if (string_defined_equal (ce->local, ce->remote)
+  if (proto_is_net(ce->proto)
+      && string_defined_equal (ce->local, ce->remote)
       && ce->local_port == ce->remote_port)
     msg (M_USAGE, "--remote and --local addresses are the same");
   
@@ -1798,16 +1824,20 @@
    */
 
 #ifdef ENABLE_FRAGMENT
-  if (ce->proto != PROTO_UDPv4 && options->fragment)
+  if (!proto_is_udp(ce->proto) && options->fragment)
     msg (M_USAGE, "--fragment can only be used with --proto udp");
 #endif
 
 #ifdef ENABLE_OCC
-  if (ce->proto != PROTO_UDPv4 && options->explicit_exit_notification)
+  if (!proto_is_udp(ce->proto) && options->explicit_exit_notification)
     msg (M_USAGE, "--explicit-exit-notify can only be used with --proto udp");
 #endif
 
-  if (!ce->remote && ce->proto == PROTO_TCPv4_CLIENT)
+  if (!ce->remote && (ce->proto == PROTO_TCPv4_CLIENT 
+#ifdef USE_PF_INET6
+		      || ce->proto == PROTO_TCPv6_CLIENT
+#endif
+		      ))
     msg (M_USAGE, "--remote MUST be used in TCP Client mode");
 
 #ifdef ENABLE_HTTP_PROXY
@@ -1825,7 +1855,12 @@
     msg (M_USAGE, "--socks-proxy can not be used in TCP Server mode");
 #endif
 
-  if (ce->proto == PROTO_TCPv4_SERVER && connection_list_defined (options))
+  if ((ce->proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+       || ce->proto == PROTO_TCPv6_SERVER
+#endif
+       )
+       && connection_list_defined (options))
     msg (M_USAGE, "TCP server mode allows at most one --remote address");
 
 #if P2MP_SERVER
@@ -1839,11 +1874,28 @@
 	msg (M_USAGE, "--mode server only works with --dev tun or --dev tap");
       if (options->pull)
 	msg (M_USAGE, "--pull cannot be used with --mode server");
-      if (!(ce->proto == PROTO_UDPv4 || ce->proto == PROTO_TCPv4_SERVER))
-	msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server");
+      if (!(proto_is_udp(ce->proto) || ce->proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	    || ce->proto == PROTO_TCPv6_SERVER
+#endif
+	    ))
+	msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server"
+#ifdef USE_PF_INET6
+	    " or proto tcp6-server"
+#endif
+	     );
 #if PORT_SHARE
-      if ((options->port_share_host || options->port_share_port) && ce->proto != PROTO_TCPv4_SERVER)
-	msg (M_USAGE, "--port-share only works in TCP server mode (--proto tcp-server)");
+      if ((options->port_share_host || options->port_share_port) && 
+            (ce->proto != PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	     && ce->proto != PROTO_TCPv6_SERVER
+#endif
+	     ))
+	msg (M_USAGE, "--port-share only works in TCP server mode (--proto tcp-server"
+#ifdef USE_PF_INET6
+	     " or tcp6-server"
+#endif
+	  ")");
 #endif
       if (!options->tls_server)
 	msg (M_USAGE, "--mode server requires --tls-server");
@@ -1871,9 +1923,17 @@
 	msg (M_USAGE, "--inetd cannot be used with --mode server");
       if (options->ipchange)
 	msg (M_USAGE, "--ipchange cannot be used with --mode server (use --client-connect instead)");
-      if (!(ce->proto == PROTO_UDPv4 || ce->proto == PROTO_TCPv4_SERVER))
-	msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server");
-      if (ce->proto != PROTO_UDPv4 && (options->cf_max || options->cf_per))
+      if (!(proto_is_dgram(ce->proto) || ce->proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	    || ce->proto == PROTO_TCPv6_SERVER
+#endif
+	    ))
+	msg (M_USAGE, "--mode server currently only supports --proto udp or --proto tcp-server"
+#ifdef USE_PF_INET6
+	    " or --proto tcp6-server"
+#endif
+	     );
+      if (!proto_is_udp(ce->proto) && (options->cf_max || options->cf_per))
 	msg (M_USAGE, "--connect-freq only works with --mode server --proto udp.  Try --max-clients instead.");
       if (!(dev == DEV_TYPE_TAP || (dev == DEV_TYPE_TUN && options->topology == TOP_SUBNET)) && options->ifconfig_pool_netmask)
 	msg (M_USAGE, "The third parameter to --ifconfig-pool (netmask) is only valid in --dev tap mode");
@@ -1964,7 +2024,7 @@
   /*
    * Check consistency of replay options
    */
-  if ((ce->proto != PROTO_UDPv4)
+  if ((!proto_is_udp(ce->proto))
       && (options->replay_window != defaults.replay_window
 	  || options->replay_time != defaults.replay_time))
     msg (M_USAGE, "--replay-window only makes sense with --proto udp");
@@ -2137,6 +2197,10 @@
     {
       if (ce->proto == PROTO_TCPv4)
 	ce->proto = PROTO_TCPv4_CLIENT;
+#ifdef USE_PF_INET6
+      else if (ce->proto == PROTO_TCPv6)
+	ce->proto = PROTO_TCPv6_CLIENT;
+#endif
     }
 #endif
 
--- ps.c
+++ ps.c
@@ -320,9 +320,9 @@
 	       const int port)
 {
   CLEAR (*osaddr);
-  osaddr->sa.sin_family = AF_INET;
-  osaddr->sa.sin_addr.s_addr = htonl (addr);
-  osaddr->sa.sin_port = htons (port);
+  osaddr->addr.in4.sin_family = AF_INET;
+  osaddr->addr.in4.sin_addr.s_addr = htonl (addr);
+  osaddr->addr.in4.sin_port = htons (port);
 }
 
 static inline void
--- README.ipv6
+++ README.ipv6
@@ -0,0 +1,81 @@
+[ Last updated: 25-Mar-2011. ]
+
+OpenVPN-2.1 over UDP6/TCP6 README for ipv6-0.4.x patch releases:
+( --udp6 and --tcp6-{client,server} )
+
+* Availability
+  Source code under GPLv2 from http://github.com/jjo/openvpn-ipv6
+
+  Distro ready repos/packages:
+  o Debian sid official repo, by Alberto Gonzalez Iniesta,
+    starting from openvpn_2.1~rc20-2
+  o Gentoo official portage tree, by Marcel Pennewiss:
+    - https://bugs.gentoo.org/show_bug.cgi?id=287896
+  o Ubuntu package, by Bernhard Schmidt:
+    - https://launchpad.net/~berni/+archive/ipv6/+packages
+  o Freetz.org, milestone freetz-1.2
+    - http://trac.freetz.org/milestone/freetz-1.2
+
+* Status:
+  o OK:
+    - upd6,tcp6: GNU/Linux, win32, openbsd-4.7, freebsd-8.1
+    - udp4->upd6,tcp4->tcp6 (ipv4/6 mapped): GNU/Linux
+      (gives a warning on local!=remote proto matching)
+  o NOT:
+    - win32: tcp4->tcp6 (ipv4/6 mapped) fails w/connection refused
+  o NOT tested:
+    - mgmt console
+
+* Build setup:
+  ./configure --enable-ipv6        (by default)
+
+* Usage:
+  For IPv6 just specify "-p upd6" an proper IPv6 hostnames, adapting the example
+  from man page ...
+
+  On may:
+    openvpn --proto udp6 --remote <june_IPv6_addr> --dev tun1 \
+      --ifconfig 10.4.0.1 10.4.0.2 --verb 5 --secret key
+
+  On june:
+    openvpn --proto udp6 --remote <may_IPv6_addr>  --dev tun1 \
+      --ifconfig 10.4.0.2 10.4.0.1 --verb 5 --secret key
+
+  Same for --proto tcp6-client, tcp6-server.
+
+* Main code changes summary:
+  - socket.h: New struct openvpn_sockaddr type that holds sockaddrs and pktinfo,
+    (here I omitted #ifdef USE_PF_xxxx, see socket.h )
+
+    struct openvpn_sockaddr {
+    	union {
+    		struct sockaddr sa;
+    		struct sockaddr_in in;
+    		struct sockaddr_in6 in6;
+    	} addr;
+    };
+
+    struct link_socket_addr
+    {
+            struct openvpn_sockaddr local;
+            struct openvpn_sockaddr remote;
+            struct openvpn_sockaddr actual;
+    };
+
+    PRO: allows simple type overloading: local.addr.sa, local.addr.in, local.addr.in6 ... etc
+    (also local.pi.in and local.pi.in6)
+
+  - several function prototypes moved from sockaddr_in to openvpn_sockaddr
+  - several new sockaddr functions needed to "generalize" AF_xxxx operations:
+    addr_copy(), addr_zero(), ...etc
+    proto_is_udp(), proto_is_dgram(), proto_is_net()
+
+* TODO: See TODO.ipv6
+
+--
+JuanJo Ciarlante   jjo () google () com ............................
+:                                                                  :
+.                                         Linux IP Aliasing author .
+.   Modular algo (AES et all) support for FreeSWAN/OpenSWAN author .
+.                                        OpenVPN over IPv6 support .
+:......     plus other scattered free software bits in the wild ...:
--- route.c
+++ route.c
@@ -581,13 +581,23 @@
 	  if (!local)
 	    {
 	      /* route remote host to original default gateway */
-	      add_route3 (rl->spec.remote_host,
-			  ~0,
-			  rl->spec.net_gateway,
-			  tt,
-			  flags,
-			  es);
-	      rl->did_local = true;
+#ifdef USE_PF_INET6
+	      /* if remote_host is not ipv4 (ie: ipv6), just skip
+	       * adding this special /32 route */
+	      if (rl->spec.remote_host != IPV4_INVALID_ADDR) {
+#endif
+		add_route3 (rl->spec.remote_host,
+			    ~0,
+			    rl->spec.net_gateway,
+			    tt,
+			    flags,
+			    es);
+		rl->did_local = true;
+#ifdef USE_PF_INET6
+	      } else {
+		dmsg (D_ROUTE, "ROUTE remote_host protocol differs from tunneled");
+	      }
+#endif
 	    }
 
 	  /* route DHCP/DNS server traffic through original default gateway */
--- socket.c
+++ socket.c
@@ -36,10 +36,16 @@
 #include "memdbg.h"
 
 const int proto_overhead[] = { /* indexed by PROTO_x */
-  IPv4_UDP_HEADER_SIZE,
+  0,
+  IPv4_UDP_HEADER_SIZE, /* IPv4 */
   IPv4_TCP_HEADER_SIZE,
   IPv4_TCP_HEADER_SIZE,
-  IPv4_TCP_HEADER_SIZE
+#ifdef USE_PF_INET6
+  IPv6_UDP_HEADER_SIZE, /* IPv6 */
+  IPv6_TCP_HEADER_SIZE,
+  IPv6_TCP_HEADER_SIZE,
+  IPv6_TCP_HEADER_SIZE,
+#endif
 };
 
 /*
@@ -276,6 +282,201 @@
   return (flags & GETADDR_HOST_ORDER) ? ntohl (ia.s_addr) : ia.s_addr;
 }
 
+#ifdef USE_PF_INET6
+/*
+ * Translate IPv6 addr or hostname into struct addrinfo
+ * If resolve error, try again for
+ * resolve_retry_seconds seconds.
+ */
+bool
+getaddr6 (unsigned int flags,
+	 const char *hostname,
+	 int resolve_retry_seconds,
+	 volatile int *signal_received,
+         int *gai_err,
+	 struct sockaddr_in6 *in6)
+{
+  bool success;
+  struct addrinfo hints, *ai;
+  int status;
+  int sigrec = 0;
+  int msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS;
+  struct gc_arena gc = gc_new ();
+
+  ASSERT(in6);
+
+  if (!hostname)
+    hostname = "::";
+
+  if (flags & GETADDR_RANDOMIZE)
+    hostname = hostname_randomize(hostname, &gc);
+
+  if (flags & GETADDR_MSG_VIRT_OUT)
+    msglevel |= M_MSG_VIRT_OUT;
+
+  CLEAR (ai);
+  success = false;
+
+  if ((flags & (GETADDR_FATAL_ON_SIGNAL|GETADDR_WARN_ON_SIGNAL))
+      && !signal_received)
+    signal_received = &sigrec;
+
+  /* try numeric ipv6 addr first */
+  CLEAR(hints);
+  hints.ai_family = AF_INET6;
+  hints.ai_flags = AI_NUMERICHOST;
+  if ((status = getaddrinfo(hostname, NULL, &hints, &ai))==0)
+    {
+      *in6 = *((struct sockaddr_in6 *)(ai->ai_addr));
+      freeaddrinfo(ai);
+      ai = NULL;
+    }
+  if (gai_err)
+    *gai_err = status;
+
+
+  if (status != 0) /* parse as IPv6 address failed? */
+    {
+      const int fail_wait_interval = 5; /* seconds */
+      int resolve_retries = (flags & GETADDR_TRY_ONCE) ? 1 : (resolve_retry_seconds / fail_wait_interval);
+      const char *fmt;
+      int level = 0;
+      int err;
+
+      ai = NULL;
+
+      fmt = "RESOLVE: Cannot resolve host address: %s: %s";
+      if ((flags & GETADDR_MENTION_RESOLVE_RETRY)
+	  && !resolve_retry_seconds)
+	fmt = "RESOLVE: Cannot resolve host address: %s: %s (I would have retried this name query if you had specified the --resolv-retry option.)";
+
+      if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL)
+	{
+	  msg (msglevel, "RESOLVE: Cannot parse IPv6 address: %s", hostname);
+	  goto done;
+	}
+
+#ifdef ENABLE_MANAGEMENT
+      if (flags & GETADDR_UPDATE_MANAGEMENT_STATE)
+	{
+	  if (management)
+	    management_set_state (management,
+				  OPENVPN_STATE_RESOLVE,
+				  NULL,
+				  (in_addr_t)0,
+				  (in_addr_t)0);
+	}
+#endif
+
+      /*
+       * Resolve hostname
+       */
+      while (true)
+	{
+	  /* try hostname lookup */
+          hints.ai_flags = 0;
+          hints.ai_socktype = dnsflags_to_socktype(flags);
+	  dmsg (D_SOCKET_DEBUG, "GETADDR6 flags=0x%04x ai_family=%d ai_socktype=%d",
+		flags, hints.ai_family, hints.ai_socktype);
+          err = getaddrinfo(hostname, NULL, &hints, &ai);
+
+          if (gai_err)
+            *gai_err = err;
+
+	  if (signal_received)
+	    {
+	      get_signal (signal_received);
+	      if (*signal_received) /* were we interrupted by a signal? */
+		{
+                  if (0 == err) {
+                    ASSERT(ai);
+                    freeaddrinfo(ai);
+                    ai = NULL;
+                  }
+		  if (*signal_received == SIGUSR1) /* ignore SIGUSR1 */
+		    {
+		      msg (level, "RESOLVE: Ignored SIGUSR1 signal received during DNS resolution attempt");
+		      *signal_received = 0;
+		    }
+		  else
+		    goto done;
+		}
+	    }
+
+	  /* success? */
+	  if (0 == err)
+	    break;
+
+	  /* resolve lookup failed, should we
+	     continue or fail? */
+
+	  level = msglevel;
+	  if (resolve_retries > 0)
+	    level = D_RESOLVE_ERRORS;
+
+	  msg (level,
+	       fmt,
+	       hostname,
+	       gai_strerror(err));
+
+	  if (--resolve_retries <= 0)
+	    goto done;
+
+	  openvpn_sleep (fail_wait_interval);
+	}
+
+      ASSERT(ai);
+
+      if (!ai->ai_next)
+        *in6 = *((struct sockaddr_in6*)(ai->ai_addr));
+      else 
+        /* more than one address returned */
+        {
+          struct addrinfo *ai_cursor;
+          int n = 0;
+          /* count address list */
+          for (ai_cursor = ai; ai_cursor; ai_cursor = ai_cursor->ai_next) n++;
+          ASSERT (n >= 2);
+
+          msg (D_RESOLVE_ERRORS, "RESOLVE: NOTE: %s resolves to %d ipv6 addresses, choosing one by random",
+               hostname,
+               n);
+
+          /* choose address randomly, for basic load-balancing capability */
+	  n--;
+          n %= get_random();
+          for (ai_cursor = ai; n; ai_cursor = ai_cursor->ai_next) n--;
+          *in6 = *((struct sockaddr_in6*)(ai_cursor->ai_addr));
+        }
+
+      freeaddrinfo(ai);
+      ai = NULL;
+
+      /* hostname resolve succeeded */
+      success = true;
+    }
+  else
+    {
+      /* IP address parse succeeded */
+      success = true;
+    }
+
+ done:
+  if (signal_received && *signal_received)
+    {
+      int level = 0;
+      if (flags & GETADDR_FATAL_ON_SIGNAL)
+	level = M_FATAL;
+      else if (flags & GETADDR_WARN_ON_SIGNAL)
+	level = M_WARN;
+      msg (level, "RESOLVE: signal received during DNS resolution attempt");
+    }
+
+  gc_free (&gc);
+  return success;
+}
+#endif /* USE_PF_INET6 */
+
 /*
  * We do our own inet_aton because the glibc function
  * isn't very good about error checking.
@@ -410,20 +611,53 @@
 	       bool *changed,
 	       const unsigned int sockflags)
 {
-  if (host && addr)
+  switch(addr->addr.sa.sa_family)
     {
-      const in_addr_t new_addr = getaddr (
-					  sf2gaf(GETADDR_RESOLVE|GETADDR_UPDATE_MANAGEMENT_STATE, sockflags),
-					  host,
-					  1,
-					  NULL,
-					  NULL);
-      if (new_addr && addr->sa.sin_addr.s_addr != new_addr)
+    case AF_INET:
+      if (host && addr)
 	{
-	  addr->sa.sin_addr.s_addr = new_addr;
-	  *changed = true;
+	  const in_addr_t new_addr = getaddr (
+					      sf2gaf(GETADDR_RESOLVE|GETADDR_UPDATE_MANAGEMENT_STATE, sockflags),
+					      host,
+					      1,
+					      NULL,
+					      NULL);
+	  if (new_addr && addr->addr.in4.sin_addr.s_addr != new_addr)
+	    {
+	      addr->addr.in4.sin_addr.s_addr = new_addr;
+	      *changed = true;
+	    }
 	}
-    }
+      break;
+#ifdef USE_PF_INET6
+    case AF_INET6:
+      if (host && addr)
+        {
+          struct sockaddr_in6 sin6;
+          CLEAR(sin6);
+          int success = getaddr6 (
+                                    sf2gaf(GETADDR_RESOLVE|GETADDR_UPDATE_MANAGEMENT_STATE, sockflags),
+                                    host,
+                                    1,
+                                    NULL,
+                                    NULL,
+                                    &sin6);
+          if ( success )
+            {
+              if (!IN6_ARE_ADDR_EQUAL(&sin6.sin6_addr, &addr->addr.in6.sin6_addr))
+              {
+                int port = addr->addr.in6.sin6_port;
+                /* ipv6 requires also eg. sin6_scope_id => easier to fully copy and override port */
+                addr->addr.in6 = sin6; 
+                addr->addr.in6.sin6_port = port;
+              }
+            }
+        }
+      break;
+#endif
+    default:
+        ASSERT(0);
+  }
 }
 
 static int
@@ -610,12 +844,62 @@
   else if (flags & SF_USE_IP_PKTINFO)
     {
       int pad = 1;
-      setsockopt (sd, SOL_IP, IP_PKTINFO, (void*)&pad, sizeof(pad));
+#ifdef IP_PKTINFO
+      if (setsockopt (sd, SOL_IP, IP_PKTINFO,
+		      (void*)&pad, sizeof(pad)) < 0)
+        msg(M_SOCKERR, "UDP: failed setsockopt for IP_PKTINFO");
+#elif defined(IP_RECVDSTADDR)
+      if (setsockopt (sd, IPPROTO_IP, IP_RECVDSTADDR,
+		      (void*)&pad, sizeof(pad)) < 0)
+        msg(M_SOCKERR, "UDP: failed setsockopt for IP_RECVDSTADDR");
+#else
+#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)
+#endif
+    }
+#endif
+  return sd;
+}
+
+#ifdef USE_PF_INET6
+static socket_descriptor_t
+create_socket_udp6 (const unsigned int flags)
+{
+  socket_descriptor_t sd;
+
+  if ((sd = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) < 0)
+    msg (M_SOCKERR, "UDP: Cannot create UDP6 socket");
+#if ENABLE_IP_PKTINFO
+  else if (flags & SF_USE_IP_PKTINFO)
+    {
+      int pad = 1;
+      if (setsockopt (sd, IPPROTO_IPV6, IPV6_PKTINFO,
+		      (void*)&pad, sizeof(pad)) < 0)
+	msg(M_SOCKERR, "UDP: failed setsockopt for IPV6_PKTINFO");
     }
 #endif
   return sd;
 }
 
+static socket_descriptor_t
+create_socket_tcp6 (void)
+{
+  socket_descriptor_t sd;
+
+  if ((sd = socket (PF_INET6, SOCK_STREAM, IPPROTO_TCP)) < 0)
+    msg (M_SOCKERR, "Cannot create TCP6 socket");
+
+  /* set SO_REUSEADDR on socket */
+  {
+    int on = 1;
+    if (setsockopt (sd, SOL_SOCKET, SO_REUSEADDR,
+		    (void *) &on, sizeof (on)) < 0)
+      msg (M_SOCKERR, "TCP: Cannot setsockopt SO_REUSEADDR on TCP6 socket");
+  }
+
+  return sd;
+}
+
+#endif
 static void
 create_socket (struct link_socket *sock)
 {
@@ -623,6 +907,7 @@
   if (sock->info.proto == PROTO_UDPv4)
     {
       sock->sd = create_socket_udp (sock->sockflags);
+      sock->sockflags |= SF_GETADDRINFO_DGRAM;
 
 #ifdef ENABLE_SOCKS
       if (sock->socks_proxy)
@@ -634,6 +919,18 @@
     {
       sock->sd = create_socket_tcp ();
     }
+#ifdef USE_PF_INET6
+  else if (sock->info.proto == PROTO_TCPv6_SERVER
+	   || sock->info.proto == PROTO_TCPv6_CLIENT)
+    {
+      sock->sd = create_socket_tcp6 ();
+    }
+  else if (sock->info.proto == PROTO_UDPv6)
+    {
+      sock->sd = create_socket_udp6 (sock->sockflags);
+      sock->sockflags |= SF_GETADDRINFO_DGRAM;
+    }
+#endif
   else
     {
       ASSERT (0);
@@ -671,7 +968,12 @@
 		  struct link_socket_actual *act,
 		  const bool nowait)
 {
-  socklen_t remote_len = sizeof (act->dest.sa);
+  /* af_addr_size WILL return 0 in this case if AFs other than AF_INET
+   * are compiled because act is empty here.
+   * could use getsockname() to support later remote_len check
+   */
+  socklen_t remote_len_af = af_addr_size(act->dest.addr.sa.sa_family);
+  socklen_t remote_len = sizeof(act->dest.addr);
   socket_descriptor_t new_sd = SOCKET_UNDEFINED;
 
   CLEAR (*act);
@@ -679,7 +981,7 @@
 #ifdef HAVE_GETPEERNAME
   if (nowait)
     {
-      new_sd = getpeername (sd, (struct sockaddr *) &act->dest.sa, &remote_len);
+      new_sd = getpeername (sd, &act->dest.addr.sa, &remote_len);
 
       if (!socket_defined (new_sd))
 	msg (D_LINK_ERRORS | M_ERRNO_SOCK, "TCP: getpeername() failed");
@@ -692,7 +994,7 @@
 #endif
   else
     {
-      new_sd = accept (sd, (struct sockaddr *) &act->dest.sa, &remote_len);
+      new_sd = accept (sd, &act->dest.addr.sa, &remote_len);
     }
 
 #if 0 /* For debugging only, test the effect of accept() failures */
@@ -708,7 +1010,8 @@
     {
       msg (D_LINK_ERRORS | M_ERRNO_SOCK, "TCP: accept(%d) failed", sd);
     }
-  else if (remote_len != sizeof (act->dest.sa))
+  /* only valid if we have remote_len_af!=0 */
+  else if (remote_len_af && remote_len != remote_len_af)
     {
       msg (D_LINK_ERRORS, "TCP: Received strange incoming connection with unknown address length=%d", remote_len);
       openvpn_close_socket (new_sd);
@@ -809,7 +1112,7 @@
 {
   struct gc_arena gc = gc_new ();
 
-  if (bind (sd, (struct sockaddr *) &local->sa, sizeof (local->sa)))
+  if (bind (sd, &local->addr.sa, af_addr_size(local->addr.sa.sa_family)))
     {
       const int errnum = openvpn_errno_socket ();
       msg (M_FATAL, "%s: Socket bind failed on local address %s: %s",
@@ -830,7 +1133,7 @@
 
 #ifdef CONNECT_NONBLOCK
   set_nonblock (sd);
-  status = connect (sd, (struct sockaddr *) &remote->sa, sizeof (remote->sa));
+  status = connect (sd, &remote->addr.sa, af_addr_size(remote->addr.sa.sa_family));
   if (status)
     status = openvpn_errno_socket ();
   if (status == EINPROGRESS)
@@ -888,7 +1191,7 @@
 	}
     }
 #else
-  status = connect (sd, (struct sockaddr *) &remote->sa, sizeof (remote->sa));
+  status = connect (sd, &remote->addr.sa, af_addr_size(remote->addr.sa.sa_family));
   if (status)
     status = openvpn_errno_socket ();
 #endif
@@ -966,7 +1269,20 @@
       if (*signal_received)
 	goto done;
 
-      *sd = create_socket_tcp ();
+#ifdef USE_PF_INET6
+      switch(local->addr.sa.sa_family)
+	{
+	case PF_INET6:
+	  *sd = create_socket_tcp6 ();
+	  break;
+	case PF_INET:
+#endif
+	  *sd = create_socket_tcp ();
+#ifdef USE_PF_INET6
+	  break;
+	}
+#endif
+
       if (bind_local)
         socket_bind (*sd, local, "TCP Client");
       update_remote (remote_dynamic, remote, remote_changed, sockflags);
@@ -1031,15 +1347,54 @@
   /* resolve local address if undefined */
   if (!addr_defined (&sock->info.lsa->local))
     {
-      sock->info.lsa->local.sa.sin_family = AF_INET;
-      sock->info.lsa->local.sa.sin_addr.s_addr =
-	(sock->local_host ? getaddr (GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL,
+#ifdef USE_PF_INET6
+      /* may return AF_{INET|INET6} guessed from local_host */
+      switch(addr_guess_family(sock->info.proto, sock->local_host))
+	{
+	case AF_INET:
+#endif
+	  sock->info.lsa->local.addr.in4.sin_family = AF_INET;
+	  sock->info.lsa->local.addr.in4.sin_addr.s_addr =
+	    (sock->local_host ? getaddr (GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL,
+					 sock->local_host,
+					 0,
+					 NULL,
+					 NULL)
+	     : htonl (INADDR_ANY));
+	  sock->info.lsa->local.addr.in4.sin_port = htons (sock->local_port);
+#ifdef USE_PF_INET6
+	  break;
+	case AF_INET6:
+	    {
+	      int success;
+	      int err;
+	      CLEAR(sock->info.lsa->local.addr.in6);
+	      if (sock->local_host)
+		{
+		  success = getaddr6(GETADDR_RESOLVE | GETADDR_WARN_ON_SIGNAL | GETADDR_FATAL,
 				     sock->local_host,
 				     0,
 				     NULL,
-				     NULL)
-	 : htonl (INADDR_ANY));
-      sock->info.lsa->local.sa.sin_port = htons (sock->local_port);
+				     &err,
+				     &sock->info.lsa->local.addr.in6);
+		}
+	      else
+		{
+		  sock->info.lsa->local.addr.in6.sin6_family = AF_INET6;
+		  sock->info.lsa->local.addr.in6.sin6_addr = in6addr_any;
+		  success = true;
+		}
+	      if (!success)
+		{
+		  msg (M_FATAL, "getaddr6() failed for local \"%s\": %s",
+		       sock->local_host,
+		       gai_strerror(err));
+		}
+	      sock->info.lsa->local.addr.in6.sin6_port = htons (sock->local_port);
+	    }
+	  break;
+	}
+#endif /* USE_PF_INET6 */
     }
   
   /* bind to local address/port */
@@ -1062,14 +1417,32 @@
 		volatile int *signal_received)
 {
   struct gc_arena gc = gc_new ();
+#ifdef USE_PF_INET6
+  int af;
+#endif
 
   if (!sock->did_resolve_remote)
     {
       /* resolve remote address if undefined */
       if (!addr_defined (&sock->info.lsa->remote))
 	{
-	  sock->info.lsa->remote.sa.sin_family = AF_INET;
-	  sock->info.lsa->remote.sa.sin_addr.s_addr = 0;
+#ifdef USE_PF_INET6
+          af = addr_guess_family(sock->info.proto, sock->remote_host);
+          switch(af)
+            {
+              case AF_INET:
+#endif
+                sock->info.lsa->remote.addr.in4.sin_family = AF_INET;
+                sock->info.lsa->remote.addr.in4.sin_addr.s_addr = 0;
+#ifdef USE_PF_INET6
+                break;
+              case AF_INET6:
+                CLEAR(sock->info.lsa->remote.addr.in6);
+                sock->info.lsa->remote.addr.in6.sin6_family = AF_INET6;
+                sock->info.lsa->remote.addr.in6.sin6_addr = in6addr_any;
+                break;
+            }
+#endif
 
 	  if (sock->remote_host)
 	    {
@@ -1112,13 +1485,31 @@
 		  ASSERT (0);
 		}
 
-	      sock->info.lsa->remote.sa.sin_addr.s_addr = getaddr (
-		    flags,
-		    sock->remote_host,
-		    retry,
-		    &status,
-		    signal_received);
-	      
+#ifdef USE_PF_INET6
+              switch(af)
+                {
+                  case AF_INET:
+#endif
+                    sock->info.lsa->remote.addr.in4.sin_addr.s_addr = getaddr (
+                          flags,
+                          sock->remote_host,
+                          retry,
+                          &status,
+                          signal_received);
+#ifdef USE_PF_INET6
+                    break;
+                  case AF_INET6:
+                    status = getaddr6 (
+                        flags,
+                        sock->remote_host,
+                        retry,
+                        signal_received,
+                        NULL,
+                        &sock->info.lsa->remote.addr.in6);
+                    break;
+                }
+#endif
+
 	      dmsg (D_SOCKET_DEBUG, "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d",
 		   flags,
 		   phase,
@@ -1138,8 +1529,19 @@
 		  goto done;
 		}
 	    }
-
-	  sock->info.lsa->remote.sa.sin_port = htons (sock->remote_port);
+#ifdef USE_PF_INET6
+          switch(af)
+            {
+              case AF_INET:
+#endif
+                sock->info.lsa->remote.addr.in4.sin_port = htons (sock->remote_port);
+#ifdef USE_PF_INET6
+                break;
+              case AF_INET6:
+                sock->info.lsa->remote.addr.in6.sin6_port = htons (sock->remote_port);
+                break;
+            }
+#endif
 	}
   
       /* should we re-use previous active remote address? */
@@ -1256,7 +1658,11 @@
   if (mode == LS_MODE_TCP_ACCEPT_FROM)
     {
       ASSERT (accept_from);
-      ASSERT (sock->info.proto == PROTO_TCPv4_SERVER);
+      ASSERT (sock->info.proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	      || sock->info.proto == PROTO_TCPv6_SERVER
+#endif
+	     );
       ASSERT (!sock->inetd);
       sock->sd = accept_from->sd;
     }
@@ -1313,7 +1719,11 @@
   /* were we started by inetd or xinetd? */
   if (sock->inetd)
     {
-      ASSERT (sock->info.proto != PROTO_TCPv4_CLIENT);
+      ASSERT (sock->info.proto != PROTO_TCPv4_CLIENT
+#ifdef USE_PF_INET6
+	      && sock->info.proto != PROTO_TCPv6_CLIENT
+#endif
+      );
       ASSERT (socket_defined (inetd_socket_descriptor));
       sock->sd = inetd_socket_descriptor;
     }
@@ -1362,7 +1772,34 @@
   /* were we started by inetd or xinetd? */
   if (sock->inetd)
     {
-      if (sock->info.proto == PROTO_TCPv4_SERVER)
+      if (sock->info.proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	  || sock->info.proto == PROTO_TCPv6_SERVER
+#endif
+      ) {
+	/* AF_INET as default (and fallback) for inetd */
+	sock->info.lsa->actual.dest.addr.sa.sa_family = AF_INET;
+#ifdef USE_PF_INET6
+#ifdef HAVE_GETSOCKNAME
+	  {
+	    /* inetd: hint family type for dest = local's */
+	    struct openvpn_sockaddr local_addr;
+	    socklen_t addrlen = sizeof(local_addr);
+	    if (getsockname (sock->sd, (struct sockaddr *)&local_addr, &addrlen) == 0) {
+	      sock->info.lsa->actual.dest.addr.sa.sa_family = local_addr.addr.sa.sa_family;
+	      dmsg (D_SOCKET_DEBUG, "inetd(%s): using sa_family=%d from getsockname(%d)",
+		    proto2ascii(sock->info.proto, false), local_addr.addr.sa.sa_family,
+		    sock->sd);
+	    } else
+	      msg (M_WARN, "inetd(%s): getsockname(%d) failed, using AF_INET",
+		   proto2ascii(sock->info.proto, false), sock->sd);
+	  }
+#else
+	msg (M_WARN, "inetd(%s): this OS does not provide the getsockname() "
+	     "function, using AF_INET",
+	     proto2ascii(sock->info.proto, false));
+#endif
+#endif
 	sock->sd =
 	  socket_listen_accept (sock->sd,
 				&sock->info.lsa->actual,
@@ -1372,6 +1809,7 @@
 				false,
 				sock->inetd == INETD_NOWAIT,
 				signal_received);
+      }
       ASSERT (!remote_changed);
       if (*signal_received)
 	goto done;
@@ -1384,7 +1822,11 @@
 	goto done;
 
       /* TCP client/server */
-      if (sock->info.proto == PROTO_TCPv4_SERVER)
+      if (sock->info.proto == PROTO_TCPv4_SERVER
+#ifdef USE_PF_INET6
+	  ||sock->info.proto == PROTO_TCPv6_SERVER
+#endif
+	)
 	{
 	  switch (sock->mode)
 	    {
@@ -1419,7 +1861,11 @@
 	      ASSERT (0);
 	    }
 	}
-      else if (sock->info.proto == PROTO_TCPv4_CLIENT)
+      else if (sock->info.proto == PROTO_TCPv4_CLIENT
+#ifdef USE_PF_INET6
+               ||sock->info.proto == PROTO_TCPv6_CLIENT
+#endif
+              )
 	{
 
 #ifdef GENERAL_PROXY_SUPPORT
@@ -1506,8 +1952,8 @@
 	  sock->remote_port = sock->proxy_dest_port;
 	  sock->did_resolve_remote = false;
 
-	  sock->info.lsa->actual.dest.sa.sin_addr.s_addr = 0;
-	  sock->info.lsa->remote.sa.sin_addr.s_addr = 0;
+	  addr_zero_host(&sock->info.lsa->actual.dest);
+	  addr_zero_host(&sock->info.lsa->remote);
 
 	  resolve_remote (sock, 1, NULL, signal_received);
 
@@ -1522,7 +1968,7 @@
       if (remote_changed)
 	{
 	  msg (M_INFO, "TCP/UDP: Dynamic remote address changed during TCP connection establishment");
-	  sock->info.lsa->remote.sa.sin_addr.s_addr = sock->info.lsa->actual.dest.sa.sin_addr.s_addr;
+	  addr_copy_host(&sock->info.lsa->remote, &sock->info.lsa->actual.dest);
 	}
     }
 
@@ -1708,13 +2154,20 @@
 {
   struct gc_arena gc = gc_new ();
 
-  msg (D_LINK_ERRORS,
-       "TCP/UDP: Incoming packet rejected from %s[%d], expected peer address: %s (allow this incoming source address/port by removing --remote or adding --float)",
-       print_link_socket_actual (from_addr, &gc),
-       (int)from_addr->dest.sa.sin_family,
-       print_sockaddr (&info->lsa->remote, &gc));
+  switch(from_addr->dest.addr.sa.sa_family)
+    {
+    case AF_INET:
+#ifdef USE_PF_INET6
+    case AF_INET6:
+#endif
+      msg (D_LINK_ERRORS,
+	   "TCP/UDP: Incoming packet rejected from %s[%d], expected peer address: %s (allow this incoming source address/port by removing --remote or adding --float)",
+	   print_link_socket_actual (from_addr, &gc),
+	   (int)from_addr->dest.addr.sa.sa_family,
+	   print_sockaddr (&info->lsa->remote, &gc));
+      break;
+    }
   buf->len = 0;
-
   gc_free (&gc);
 }
 
@@ -1729,10 +2182,25 @@
 {
   const struct link_socket_addr *lsa = info->lsa;
 
+/* 
+ * This logic supports "redirect-gateway" semantic, which 
+ * makes sense only for PF_INET routes over PF_INET endpoints
+ *
+ * Maybe in the future consider PF_INET6 endpoints also ...
+ * by now just ignore it
+ *
+ */
+#ifdef USE_PF_INET6
+  if (lsa->actual.dest.addr.sa.sa_family != AF_INET)
+    return IPV4_INVALID_ADDR;
+#else
+  ASSERT (lsa->actual.dest.addr.sa.sa_family == AF_INET);
+#endif
+
   if (link_socket_actual_defined (&lsa->actual))
-    return ntohl (lsa->actual.dest.sa.sin_addr.s_addr);
+    return ntohl (lsa->actual.dest.addr.in4.sin_addr.s_addr);
   else if (addr_defined (&lsa->remote))
-    return ntohl (lsa->remote.sa.sin_addr.s_addr);
+    return ntohl (lsa->remote.addr.in4.sin_addr.s_addr);
   else
     return 0;
 }
@@ -1959,26 +2427,61 @@
 		   const unsigned int flags,
 		   struct gc_arena *gc)
 {
-  if (addr)
+  struct buffer out = alloc_buf_gc (128, gc);
+  bool addr_is_defined;
+  addr_is_defined = addr_defined (addr);
+  if (!addr_is_defined) {
+    return "[undef]";
+  }
+#ifdef USE_PF_INET6
+  switch(addr->addr.sa.sa_family)
     {
-      struct buffer out = alloc_buf_gc (64, gc);
-      const int port = ntohs (addr->sa.sin_port);
+    case AF_INET:
+#endif
+	{
+	  const int port= ntohs (addr->addr.in4.sin_port);
+	  buf_puts (&out, "[AF_INET]");
 
-      if (!(flags & PS_DONT_SHOW_ADDR))
-	buf_printf (&out, "%s", (addr_defined (addr) ? inet_ntoa (addr->sa.sin_addr) : "[undef]"));
+	  if (!(flags & PS_DONT_SHOW_ADDR))
+	    buf_printf (&out, "%s", (addr_defined (addr) ? inet_ntoa (addr->addr.in4.sin_addr) : "[undef]"));
 
-      if (((flags & PS_SHOW_PORT) || (addr_defined (addr) && (flags & PS_SHOW_PORT_IF_DEFINED)))
-	  && port)
+	  if (((flags & PS_SHOW_PORT) || (addr_defined (addr) && (flags & PS_SHOW_PORT_IF_DEFINED)))
+	      && port)
+	    {
+	      if (separator)
+		buf_printf (&out, "%s", separator);
+
+	      buf_printf (&out, "%d", port);
+	    }
+	}
+#ifdef USE_PF_INET6
+      break;
+    case AF_INET6:
 	{
-	  if (separator)
-	    buf_printf (&out, "%s", separator);
+	  const int port= ntohs (addr->addr.in6.sin6_port);
+	  char buf[INET6_ADDRSTRLEN] = "";
+	  buf_puts (&out, "[AF_INET6]");
+	  if (addr_is_defined)
+	    {
+	      getnameinfo(&addr->addr.sa, sizeof (struct sockaddr_in6),
+			  buf, sizeof (buf), NULL, 0, NI_NUMERICHOST);
+	      buf_puts (&out, buf);
+	    }
+	  if (((flags & PS_SHOW_PORT) || (addr_is_defined && (flags & PS_SHOW_PORT_IF_DEFINED)))
+	      && port)
+	    {
+	      if (separator)
+		buf_puts (&out, separator);
 
-	  buf_printf (&out, "%d", port);
+	      buf_printf (&out, "%d", port);
+	    }
 	}
-      return BSTR (&out);
+      break;
+    default:
+      ASSERT(0);
     }
-  else
-    return "[NULL]";
+#endif
+  return BSTR (&out);
 }
 
 const char *
@@ -1987,6 +2490,10 @@
   return print_link_socket_actual_ex (act, ":", PS_SHOW_PORT|PS_SHOW_PKTINFO, gc);
 }
 
+#ifndef IF_NAMESIZE
+#define IF_NAMESIZE 16
+#endif
+
 const char *
 print_link_socket_actual_ex (const struct link_socket_actual *act,
 			     const char *separator,
@@ -1995,15 +2502,54 @@
 {
   if (act)
     {
+      char ifname[IF_NAMESIZE] = "[undef]";
       struct buffer out = alloc_buf_gc (128, gc);
       buf_printf (&out, "%s", print_sockaddr_ex (&act->dest, separator, flags, gc));
 #if ENABLE_IP_PKTINFO
-      if ((flags & PS_SHOW_PKTINFO) && act->pi.ipi_spec_dst.s_addr)
+      if ((flags & PS_SHOW_PKTINFO) && addr_defined_ipi(act))
 	{
-	  struct openvpn_sockaddr sa;
-	  CLEAR (sa);
-	  sa.sa.sin_addr = act->pi.ipi_spec_dst;
-	  buf_printf (&out, " (via %s)", print_sockaddr_ex (&sa, separator, 0, gc));
+#ifdef USE_PF_INET6
+	  switch(act->dest.addr.sa.sa_family)
+	    {
+	    case AF_INET:
+#endif
+		{
+		  struct openvpn_sockaddr sa;
+		  CLEAR (sa);
+		  sa.addr.in4.sin_family = AF_INET;
+#ifdef IP_PKTINFO
+		  sa.addr.in4.sin_addr = act->pi.in4.ipi_spec_dst;
+		  if_indextoname(act->pi.in4.ipi_ifindex, ifname);
+#elif defined(IP_RECVDSTADDR)
+		  sa.addr.in4.sin_addr = act->pi.in4;
+		  ifname[0]=0;
+#else
+#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)
+#endif
+		  buf_printf (&out, " (via %s%%%s)",
+			      print_sockaddr_ex (&sa, separator, 0, gc),
+			      ifname);
+		}
+#ifdef USE_PF_INET6
+	      break;
+	    case AF_INET6:
+		{
+		  struct sockaddr_in6 sin6;
+		  char buf[INET6_ADDRSTRLEN] = "[undef]";
+		  CLEAR(sin6);
+		  sin6.sin6_family = AF_INET6;
+		  sin6.sin6_addr = act->pi.in6.ipi6_addr;
+		  if_indextoname(act->pi.in6.ipi6_ifindex, ifname);
+		  if (getnameinfo((struct sockaddr *)&sin6, sizeof (struct sockaddr_in6),
+				  buf, sizeof (buf), NULL, 0, NI_NUMERICHOST) == 0)
+		    buf_printf (&out, " (via %s%%%s)", buf, ifname);
+		  else
+		    buf_printf (&out, " (via [getnameinfo() err]%%%s)", ifname);
+		}
+	      break;
+	    }
+#endif /* USE_PF_INET6 */
+
 	}
 #endif
       return BSTR (&out);
@@ -2038,18 +2584,40 @@
 {
   char name_buf[256];
 
-  if (flags & SA_IP_PORT)
-    openvpn_snprintf (name_buf, sizeof (name_buf), "%s_ip", name_prefix);
-  else
-    openvpn_snprintf (name_buf, sizeof (name_buf), "%s", name_prefix);
+#ifdef USE_PF_INET6
+  char buf[128];
+  switch(addr->addr.sa.sa_family)
+    {
+    case AF_INET:
+#endif
+      if (flags & SA_IP_PORT)
+	openvpn_snprintf (name_buf, sizeof (name_buf), "%s_ip", name_prefix);
+      else
+	openvpn_snprintf (name_buf, sizeof (name_buf), "%s", name_prefix);
 
-  setenv_str (es, name_buf, inet_ntoa (addr->sa.sin_addr));
+      setenv_str (es, name_buf, inet_ntoa (addr->addr.in4.sin_addr));
 
-  if ((flags & SA_IP_PORT) && addr->sa.sin_port)
-    {
-      openvpn_snprintf (name_buf, sizeof (name_buf), "%s_port", name_prefix);
-      setenv_int (es, name_buf, ntohs (addr->sa.sin_port));
+      if ((flags & SA_IP_PORT) && addr->addr.in4.sin_port)
+	{
+	  openvpn_snprintf (name_buf, sizeof (name_buf), "%s_port", name_prefix);
+	  setenv_int (es, name_buf, ntohs (addr->addr.in4.sin_port));
+	}
+#ifdef USE_PF_INET6
+      break;
+    case AF_INET6:
+      openvpn_snprintf (name_buf, sizeof (name_buf), "%s_ip6", name_prefix);
+      getnameinfo(&addr->addr.sa, sizeof (struct sockaddr_in6),
+		  buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
+      setenv_str (es, name_buf, buf);
+
+      if ((flags & SA_IP_PORT) && addr->addr.in6.sin6_port)
+	{
+	  openvpn_snprintf (name_buf, sizeof (name_buf), "%s_port", name_prefix);
+	  setenv_int (es, name_buf, ntohs (addr->addr.in6.sin6_port));
+	}
+      break;
     }
+#endif
 }
 
 void
@@ -2059,7 +2627,8 @@
     {
       struct openvpn_sockaddr si;
       CLEAR (si);
-      si.sa.sin_addr.s_addr = htonl (addr);
+      si.addr.in4.sin_family = AF_INET;
+      si.addr.in4.sin_addr.s_addr = htonl (addr);
       setenv_sockaddr (es, name_prefix, &si, flags);
     }
 }
@@ -2080,16 +2649,63 @@
 struct proto_names {
   const char *short_form;
   const char *display_form;
+  bool	is_dgram;
+  bool	is_net;
+  unsigned short proto_af;
 };
 
 /* Indexed by PROTO_x */
-static const struct proto_names proto_names[] = {
-  {"udp",        "UDPv4"},
-  {"tcp-server", "TCPv4_SERVER"},
-  {"tcp-client", "TCPv4_CLIENT"},
-  {"tcp",        "TCPv4"}
+static const struct proto_names proto_names[PROTO_N] = {
+  {"proto-uninitialized",        "proto-NONE",0,0, AF_UNSPEC},
+  {"udp",        "UDPv4",1,1, AF_INET},
+  {"tcp-server", "TCPv4_SERVER",0,1, AF_INET},
+  {"tcp-client", "TCPv4_CLIENT",0,1, AF_INET},
+  {"tcp",        "TCPv4",0,1, AF_INET},
+#ifdef USE_PF_INET6
+  {"udp6"       ,"UDPv6",1,1, AF_INET6},
+  {"tcp6-server","TCPv6_SERVER",0,1, AF_INET6},
+  {"tcp6-client","TCPv6_CLIENT",0,1, AF_INET6},
+  {"tcp6"       ,"TCPv6",0,1, AF_INET6},
+#endif
 };
 
+bool
+proto_is_net(int proto)
+{
+  if (proto < 0 || proto >= PROTO_N)
+    ASSERT(0);
+  return proto_names[proto].is_net;
+}
+bool
+proto_is_dgram(int proto)
+{
+  if (proto < 0 || proto >= PROTO_N)
+    ASSERT(0);
+  return proto_names[proto].is_dgram;
+}
+bool
+proto_is_udp(int proto)
+{
+  if (proto < 0 || proto >= PROTO_N)
+    ASSERT(0);
+  return proto_names[proto].is_dgram&&proto_names[proto].is_net;
+}
+bool
+proto_is_tcp(int proto)
+{
+  if (proto < 0 || proto >= PROTO_N)
+    ASSERT(0);
+  return (!proto_names[proto].is_dgram)&&proto_names[proto].is_net;
+}
+
+unsigned short 
+proto_sa_family(int proto)
+{
+  if (proto < 0 || proto >= PROTO_N)
+    ASSERT(0);
+  return proto_names[proto].proto_af;
+}
+
 int
 ascii2proto (const char* proto_name)
 {
@@ -2129,6 +2745,45 @@
   return BSTR (&out);
 }
 
+int
+addr_guess_family(int proto, const char *name) 
+{
+#ifdef USE_PF_INET6
+  unsigned short ret;
+#endif
+  if (proto)
+    {
+      return proto_sa_family(proto);	/* already stamped */
+    } 
+#ifdef USE_PF_INET6
+  else
+    {
+      struct addrinfo hints , *ai;
+      int err;
+      CLEAR(hints);
+      hints.ai_flags = AI_NUMERICHOST;
+      err = getaddrinfo(name, NULL, &hints, &ai);
+      if ( 0 == err )
+	{
+	  ret=ai->ai_family;
+	  freeaddrinfo(ai);
+	  return ret;
+	}
+    }
+#endif
+  return AF_INET;	/* default */
+}
+const char *
+addr_family_name (int af) 
+{
+  switch (af)
+    {
+    case AF_INET:  return "AF_INET";
+    case AF_INET6: return "AF_INET6";
+    }
+  return "AF_UNSPEC";
+}
+
 /*
  * Given a local proto, return local proto
  * if !remote, or compatible remote proto
@@ -2143,10 +2798,15 @@
   ASSERT (proto >= 0 && proto < PROTO_N);
   if (remote)
     {
-      if (proto == PROTO_TCPv4_SERVER)
-	return PROTO_TCPv4_CLIENT;
-      if (proto == PROTO_TCPv4_CLIENT)
-	return PROTO_TCPv4_SERVER;
+      switch (proto)
+      {
+	case PROTO_TCPv4_SERVER: return PROTO_TCPv4_CLIENT;
+	case PROTO_TCPv4_CLIENT: return PROTO_TCPv4_SERVER;
+#ifdef USE_PF_INET6
+	case PROTO_TCPv6_SERVER: return PROTO_TCPv6_CLIENT;
+	case PROTO_TCPv6_CLIENT: return PROTO_TCPv6_SERVER;
+#endif
+      }
     }
   return proto;
 }
@@ -2205,10 +2865,29 @@
 #if ENABLE_IP_PKTINFO
 
 #pragma pack(1) /* needed to keep structure size consistent for 32 vs. 64-bit architectures */
-struct openvpn_pktinfo
+struct openvpn_in4_pktinfo
+{
+  struct cmsghdr cmsghdr;
+#ifdef HAVE_IN_PKTINFO
+  struct in_pktinfo pi4;
+#endif
+#ifdef IP_RECVDSTADDR
+  struct in_addr pi4;
+#endif
+};
+#ifdef USE_PF_INET6
+struct openvpn_in6_pktinfo
 {
   struct cmsghdr cmsghdr;
-  struct in_pktinfo in_pktinfo;
+  struct in6_pktinfo pi6;
+};
+#endif
+
+union openvpn_pktinfo {
+	struct openvpn_in4_pktinfo msgpi4;
+#ifdef USE_PF_INET6
+	struct openvpn_in6_pktinfo msgpi6;
+#endif
 };
 #pragma pack()
 
@@ -2219,18 +2898,18 @@
 				    struct link_socket_actual *from)
 {
   struct iovec iov;
-  struct openvpn_pktinfo opi;
+  union openvpn_pktinfo opi;
   struct msghdr mesg;
-  socklen_t fromlen = sizeof (from->dest.sa);
+  socklen_t fromlen = sizeof (from->dest.addr);
 
   iov.iov_base = BPTR (buf);
   iov.iov_len = maxsize;
   mesg.msg_iov = &iov;
   mesg.msg_iovlen = 1;
-  mesg.msg_name = &from->dest.sa;
+  mesg.msg_name = &from->dest.addr;
   mesg.msg_namelen = fromlen;
   mesg.msg_control = &opi;
-  mesg.msg_controllen = sizeof (opi);
+  mesg.msg_controllen = sizeof opi;
   buf->len = recvmsg (sock->sd, &mesg, 0);
   if (buf->len >= 0)
     {
@@ -2239,14 +2918,39 @@
       cmsg = CMSG_FIRSTHDR (&mesg);
       if (cmsg != NULL
 	  && CMSG_NXTHDR (&mesg, cmsg) == NULL
+#ifdef IP_PKTINFO
 	  && cmsg->cmsg_level == SOL_IP 
 	  && cmsg->cmsg_type == IP_PKTINFO
-	  && cmsg->cmsg_len >= sizeof (opi))
+#elif defined(IP_RECVDSTADDR)
+	  && cmsg->cmsg_level == IPPROTO_IP
+	  && cmsg->cmsg_type == IP_RECVDSTADDR
+#else
+#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)
+#endif
+	  && cmsg->cmsg_len >= sizeof (struct openvpn_in4_pktinfo))
 	{
+#ifdef IP_PKTINFO
 	  struct in_pktinfo *pkti = (struct in_pktinfo *) CMSG_DATA (cmsg);
-	  from->pi.ipi_ifindex = pkti->ipi_ifindex;
-	  from->pi.ipi_spec_dst = pkti->ipi_spec_dst;
+	  from->pi.in4.ipi_ifindex = pkti->ipi_ifindex;
+	  from->pi.in4.ipi_spec_dst = pkti->ipi_spec_dst;
+#elif defined(IP_RECVDSTADDR)
+	  from->pi.in4 = *(struct in_addr*) CMSG_DATA (cmsg);
+#else
+#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)
+#endif
 	}
+#ifdef USE_PF_INET6
+      else if (cmsg != NULL
+	  && CMSG_NXTHDR (&mesg, cmsg) == NULL
+	  && cmsg->cmsg_level == IPPROTO_IPV6 
+	  && cmsg->cmsg_type == IPV6_PKTINFO
+	  && cmsg->cmsg_len >= sizeof (struct openvpn_in6_pktinfo))
+	{
+	  struct in6_pktinfo *pkti6 = (struct in6_pktinfo *) CMSG_DATA (cmsg);
+	  from->pi.in6.ipi6_ifindex = pkti6->ipi6_ifindex;
+	  from->pi.in6.ipi6_addr = pkti6->ipi6_addr;
+	}
+#endif
     }
   return fromlen;
 }
@@ -2258,18 +2962,20 @@
 			    int maxsize,
 			    struct link_socket_actual *from)
 {
-  socklen_t fromlen = sizeof (from->dest.sa);
-  from->dest.sa.sin_addr.s_addr = 0;
+  socklen_t fromlen = sizeof (from->dest.addr);
+  socklen_t expectedlen = af_addr_size(proto_sa_family(sock->info.proto));
+  addr_zero_host(&from->dest);
   ASSERT (buf_safe (buf, maxsize));
 #if ENABLE_IP_PKTINFO
-  if (sock->sockflags & SF_USE_IP_PKTINFO)
+  /* Both PROTO_UDPv4 and PROTO_UDPv6 */
+  if (proto_is_udp(sock->info.proto) && sock->sockflags & SF_USE_IP_PKTINFO)
     fromlen = link_socket_read_udp_posix_recvmsg (sock, buf, maxsize, from);
   else
 #endif
     buf->len = recvfrom (sock->sd, BPTR (buf), maxsize, 0,
-			 (struct sockaddr *) &from->dest.sa, &fromlen);
-  if (fromlen != sizeof (from->dest.sa))
-    bad_address_length (fromlen, sizeof (from->dest.sa));
+			 &from->dest.addr.sa, &fromlen);
+  if (buf->len >= 0 && expectedlen && fromlen != expectedlen)
+    bad_address_length (fromlen, expectedlen);
   return buf->len;
 }
 
@@ -2306,26 +3012,64 @@
   struct iovec iov;
   struct msghdr mesg;
   struct cmsghdr *cmsg;
-  struct in_pktinfo *pkti;
-  struct openvpn_pktinfo opi;
 
   iov.iov_base = BPTR (buf);
   iov.iov_len = BLEN (buf);
   mesg.msg_iov = &iov;
   mesg.msg_iovlen = 1;
-  mesg.msg_name = &to->dest.sa;
-  mesg.msg_namelen = sizeof (to->dest.sa);
-  mesg.msg_control = &opi;
-  mesg.msg_controllen = sizeof (opi);
-  mesg.msg_flags = 0;
-  cmsg = CMSG_FIRSTHDR (&mesg);
-  cmsg->cmsg_len = sizeof (opi);
-  cmsg->cmsg_level = SOL_IP;
-  cmsg->cmsg_type = IP_PKTINFO;
-  pkti = (struct in_pktinfo *) CMSG_DATA (cmsg);
-  pkti->ipi_ifindex = to->pi.ipi_ifindex;
-  pkti->ipi_spec_dst = to->pi.ipi_spec_dst;
-  pkti->ipi_addr.s_addr = 0;
+  switch (sock->info.lsa->remote.addr.sa.sa_family)
+    {
+    case AF_INET:
+      {
+        struct openvpn_in4_pktinfo msgpi4;
+        mesg.msg_name = &to->dest.addr.sa;
+        mesg.msg_namelen = sizeof (struct sockaddr_in);
+        mesg.msg_control = &msgpi4;
+        mesg.msg_controllen = sizeof msgpi4;
+        mesg.msg_flags = 0;
+        cmsg = CMSG_FIRSTHDR (&mesg);
+        cmsg->cmsg_len = sizeof (struct openvpn_in4_pktinfo);
+#ifdef HAVE_IN_PKTINFO
+        cmsg->cmsg_level = SOL_IP;
+        cmsg->cmsg_type = IP_PKTINFO;
+	{
+        struct in_pktinfo *pkti;
+        pkti = (struct in_pktinfo *) CMSG_DATA (cmsg);
+        pkti->ipi_ifindex = to->pi.in4.ipi_ifindex;
+        pkti->ipi_spec_dst = to->pi.in4.ipi_spec_dst;
+        pkti->ipi_addr.s_addr = 0;
+	}
+#elif defined(IP_RECVDSTADDR)
+        cmsg->cmsg_level = IPPROTO_IP;
+        cmsg->cmsg_type = IP_RECVDSTADDR;
+        *(struct in_addr *) CMSG_DATA (cmsg) = to->pi.in4;
+#else
+#error ENABLE_IP_PKTINFO is set without IP_PKTINFO xor IP_RECVDSTADDR (fix syshead.h)
+#endif
+        break;
+      }
+#ifdef USE_PF_INET6
+    case AF_INET6:
+      {
+        struct openvpn_in6_pktinfo msgpi6;
+        struct in6_pktinfo *pkti6;
+        mesg.msg_name = &to->dest.addr.sa;
+        mesg.msg_namelen = sizeof (struct sockaddr_in6);
+        mesg.msg_control = &msgpi6;
+        mesg.msg_controllen = sizeof msgpi6;
+        mesg.msg_flags = 0;
+        cmsg = CMSG_FIRSTHDR (&mesg);
+        cmsg->cmsg_len = sizeof (struct openvpn_in6_pktinfo);
+        cmsg->cmsg_level = IPPROTO_IPV6;
+        cmsg->cmsg_type = IPV6_PKTINFO;
+        pkti6 = (struct in6_pktinfo *) CMSG_DATA (cmsg);
+        pkti6->ipi6_ifindex = to->pi.in6.ipi6_ifindex;
+        pkti6->ipi6_addr = to->pi.in6.ipi6_addr;
+        break;
+      }
+#endif
+    default: ASSERT(0);
+    }
   return sendmsg (sock->sd, &mesg, 0);
 }
 
@@ -2346,11 +3090,11 @@
       int status;
 
       /* reset buf to its initial state */
-      if (sock->info.proto == PROTO_UDPv4)
+      if (proto_is_udp(sock->info.proto))
 	{
 	  sock->reads.buf = sock->reads.buf_init;
 	}
-      else if (sock->info.proto == PROTO_TCPv4_CLIENT || sock->info.proto == PROTO_TCPv4_SERVER)
+      else if (proto_is_tcp(sock->info.proto))
 	{
 	  stream_buf_get_next (&sock->stream_buf, &sock->reads.buf);
 	}
@@ -2370,10 +3114,15 @@
       ASSERT (ResetEvent (sock->reads.overlapped.hEvent));
       sock->reads.flags = 0;
 
-      if (sock->info.proto == PROTO_UDPv4)
+      if (proto_is_udp(sock->info.proto))
 	{
 	  sock->reads.addr_defined = true;
-	  sock->reads.addrlen = sizeof (sock->reads.addr);
+#ifdef USE_PF_INET6
+	  if (sock->info.proto == PROTO_UDPv6)
+	    sock->reads.addrlen = sizeof (sock->reads.addr6);
+	  else
+#endif
+	    sock->reads.addrlen = sizeof (sock->reads.addr);
 	  status = WSARecvFrom(
 			       sock->sd,
 			       wsabuf,
@@ -2385,7 +3134,7 @@
 			       &sock->reads.overlapped,
 			       NULL);
 	}
-      else if (sock->info.proto == PROTO_TCPv4_CLIENT || sock->info.proto == PROTO_TCPv4_SERVER)
+      else if (proto_is_tcp(sock->info.proto))
 	{
 	  sock->reads.addr_defined = false;
 	  status = WSARecv(
@@ -2405,8 +3154,14 @@
 
       if (!status) /* operation completed immediately? */
 	{
+#ifdef USE_PF_INET6
+	  int addrlen = af_addr_size(sock->info.lsa->local.addr.sa.sa_family);
+	  if (sock->reads.addr_defined && sock->reads.addrlen != addrlen)
+	    bad_address_length (sock->reads.addrlen, addrlen);
+#else
 	  if (sock->reads.addr_defined && sock->reads.addrlen != sizeof (sock->reads.addr))
 	    bad_address_length (sock->reads.addrlen, sizeof (sock->reads.addr));
+#endif
 
 	  sock->reads.iostate = IOSTATE_IMMEDIATE_RETURN;
 
@@ -2465,12 +3220,22 @@
       ASSERT (ResetEvent (sock->writes.overlapped.hEvent));
       sock->writes.flags = 0;
 
-      if (sock->info.proto == PROTO_UDPv4)
+      if (proto_is_udp(sock->info.proto))
 	{
 	  /* set destination address for UDP writes */
 	  sock->writes.addr_defined = true;
-	  sock->writes.addr = to->dest.sa;
-	  sock->writes.addrlen = sizeof (sock->writes.addr);
+#ifdef USE_PF_INET6
+	  if (sock->info.proto == PROTO_UDPv6)
+	    {
+	      sock->writes.addr6 = to->dest.addr.in6;
+	      sock->writes.addrlen = sizeof (sock->writes.addr6);
+	    }
+	  else
+#endif
+	    {
+	      sock->writes.addr = to->dest.addr.in4;
+	      sock->writes.addrlen = sizeof (sock->writes.addr);
+	    }
 
 	  status = WSASendTo(
 			       sock->sd,
@@ -2483,7 +3248,7 @@
 			       &sock->writes.overlapped,
 			       NULL);
 	}
-      else if (sock->info.proto == PROTO_TCPv4_CLIENT || sock->info.proto == PROTO_TCPv4_SERVER)
+      else if (proto_is_tcp(sock->info.proto))
 	{
 	  /* destination address for TCP writes was established on connection initiation */
 	  sock->writes.addr_defined = false;
@@ -2622,13 +3387,44 @@
   if (from)
     {
       if (ret >= 0 && io->addr_defined)
+#ifdef USE_PF_INET6
+	{
+	  /* TODO(jjo): streamline this mess */
+	  /* in this func we dont have relevant info about the PF_ of this
+	   * endpoint, as link_socket_actual will be zero for the 1st received packet
+	   *
+	   * Test for inets PF_ possible sizes
+	   */
+	  switch (io->addrlen)
+	    {
+	    case sizeof(struct sockaddr_in):
+	    case sizeof(struct sockaddr_in6):
+	    /* TODO(jjo): for some reason (?) I'm getting 24,28 for AF_INET6 */ 
+	    case sizeof(struct sockaddr_in6)-4:
+	      break;
+	    default:
+	      bad_address_length (io->addrlen, af_addr_size(io->addr.sin_family));
+	    }
+
+	  switch (io->addr.sin_family)
+	    {
+	    case AF_INET:
+	      from->dest.addr.in4 = io->addr;
+	      break;
+	    case AF_INET6:
+	      from->dest.addr.in6 = io->addr6;
+	      break;
+	    }
+	}
+#else
 	{
 	  if (io->addrlen != sizeof (io->addr))
 	    bad_address_length (io->addrlen, sizeof (io->addr));
-	  from->dest.sa = io->addr;
+	  from->dest.addr.in4 = io->addr;
 	}
+#endif
       else
-	CLEAR (from->dest.sa);
+	CLEAR (from->dest.addr);
     }
   
   if (buf)
--- socket.h
+++ socket.h
@@ -70,7 +70,13 @@
 struct openvpn_sockaddr
 {
   /*int dummy;*/ /* add offset to force a bug if sa not explicitly dereferenced */
-  struct sockaddr_in sa;
+  union {
+    struct sockaddr sa;
+    struct sockaddr_in in4;
+#ifdef USE_PF_INET6
+    struct sockaddr_in6 in6;
+#endif
+  } addr;
 };
 
 /* actual address of remote, based on source address of received packets */
@@ -79,7 +85,17 @@
   /*int dummy;*/ /* add offset to force a bug if dest not explicitly dereferenced */
   struct openvpn_sockaddr dest;
 #if ENABLE_IP_PKTINFO
-  struct in_pktinfo pi;
+  union {
+#ifdef HAVE_IN_PKTINFO
+    struct in_pktinfo in4;
+#endif
+#ifdef IP_RECVDSTADDR
+    struct in_addr in4;
+#endif
+#ifdef USE_PF_INET6
+    struct in6_pktinfo in6;
+#endif
+  } pi;
 #endif
 };
 
@@ -199,6 +215,7 @@
 # define SF_TCP_NODELAY (1<<1)
 # define SF_PORT_SHARE (1<<2)
 # define SF_HOST_RANDOMIZE (1<<3)
+# define SF_GETADDRINFO_DGRAM (1<<4)
   unsigned int sockflags;
 
   /* for stream sockets */
@@ -371,6 +388,12 @@
 
 void bad_address_length (int actual, int expected);
 
+#ifdef USE_PF_INET6
+/* IPV4_INVALID_ADDR: returned by link_socket_current_remote()
+ * to ease redirect-gateway logic for ipv4 tunnels on ipv6 endpoints
+ */
+#define IPV4_INVALID_ADDR 0xffffffff
+#endif
 in_addr_t link_socket_current_remote (const struct link_socket_info *info);
 
 void link_socket_connection_initiated (const struct buffer *buf,
@@ -410,6 +433,14 @@
 socket_descriptor_t socket_do_accept (socket_descriptor_t sd,
 				      struct link_socket_actual *act,
 				      const bool nowait);
+/*
+ * proto related
+ */
+bool proto_is_net(int proto);
+bool proto_is_dgram(int proto);
+bool proto_is_udp(int proto);
+bool proto_is_tcp(int proto);
+
 
 #if UNIX_SOCK_SUPPORT
 
@@ -455,6 +486,11 @@
 #define GETADDR_UPDATE_MANAGEMENT_STATE (1<<8)
 #define GETADDR_RANDOMIZE             (1<<9)
 
+/* [ab]use flags bits to get socktype info downstream */
+/* TODO(jjo): resolve tradeoff between hackiness|args-overhead */
+#define GETADDR_DGRAM                 (1<<10)
+#define dnsflags_to_socktype(flags) ((flags & GETADDR_DGRAM) ? SOCK_DGRAM : SOCK_STREAM)
+
 in_addr_t getaddr (unsigned int flags,
 		   const char *hostname,
 		   int resolve_retry_seconds,
@@ -472,23 +508,38 @@
  * Transport protocol naming and other details.
  */
 
-#define PROTO_UDPv4        0
-#define PROTO_TCPv4_SERVER 1
-#define PROTO_TCPv4_CLIENT 2
-#define PROTO_TCPv4        3
-#define PROTO_N            4
+/* 
+ * Use enum's instead of #define to allow for easier
+ * optional proto support
+ */
+enum proto_num {
+	PROTO_NONE, /* catch for uninitialized */
+	PROTO_UDPv4,
+	PROTO_TCPv4_SERVER,
+	PROTO_TCPv4_CLIENT,
+	PROTO_TCPv4,
+#ifdef USE_PF_INET6
+	PROTO_UDPv6,
+	PROTO_TCPv6_SERVER,
+	PROTO_TCPv6_CLIENT,
+	PROTO_TCPv6,
+#endif
+	PROTO_N
+};
 
 int ascii2proto (const char* proto_name);
 const char *proto2ascii (int proto, bool display_form);
 const char *proto2ascii_all (struct gc_arena *gc);
 int proto_remote (int proto, bool remote);
+const char *addr_family_name(int af);
 
 /*
  * Overhead added to packets by various protocols.
  */
 #define IPv4_UDP_HEADER_SIZE              28
 #define IPv4_TCP_HEADER_SIZE              40
-#define IPv6_UDP_HEADER_SIZE              40
+#define IPv6_UDP_HEADER_SIZE              48
+#define IPv6_TCP_HEADER_SIZE              60
 
 extern const int proto_overhead[];
 
@@ -518,7 +569,7 @@
 static inline bool
 link_socket_proto_connection_oriented (int proto)
 {
-  return proto == PROTO_TCPv4_SERVER || proto == PROTO_TCPv4_CLIENT;
+  return !proto_is_dgram(proto);
 }
 
 static inline bool
@@ -533,7 +584,36 @@
 static inline bool
 addr_defined (const struct openvpn_sockaddr *addr)
 {
-  return addr->sa.sin_addr.s_addr != 0;
+  if (!addr) return 0;
+  switch (addr->addr.sa.sa_family) {
+    case AF_INET: return addr->addr.in4.sin_addr.s_addr != 0;
+#ifdef USE_PF_INET6
+    case AF_INET6: return !IN6_IS_ADDR_UNSPECIFIED(&addr->addr.in6.sin6_addr);
+#endif
+    default: return 0;
+  }
+}
+static inline bool
+addr_defined_ipi (const struct link_socket_actual *lsa)
+{
+#if ENABLE_IP_PKTINFO
+  if (!lsa) return 0;
+  switch (lsa->dest.addr.sa.sa_family) {
+#ifdef HAVE_IN_PKTINFO
+    case AF_INET: return lsa->pi.in4.ipi_spec_dst.s_addr != 0;
+#endif
+#ifdef IP_RECVDSTADDR
+    case AF_INET: return lsa->pi.in4.s_addr != 0;
+#endif
+#ifdef USE_PF_INET6
+    case AF_INET6: return !IN6_IS_ADDR_UNSPECIFIED(&lsa->pi.in6.ipi6_addr);
+#endif
+    default: return 0;
+  }
+#else
+  ASSERT(0);
+#endif
+  return false;
 }
 
 static inline bool
@@ -545,20 +625,50 @@
 static inline bool
 addr_match (const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2)
 {
-  return a1->sa.sin_addr.s_addr == a2->sa.sin_addr.s_addr;
+  switch(a1->addr.sa.sa_family) {
+    case AF_INET:
+      return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr;
+#ifdef USE_PF_INET6
+    case AF_INET6:
+      return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr);
+#endif
+  }
+  ASSERT(0);
+  return false;
 }
 
 static inline in_addr_t
-addr_host (const struct openvpn_sockaddr *s)
+addr_host (const struct openvpn_sockaddr *addr)
 {
-  return ntohl (s->sa.sin_addr.s_addr);
+  /* 
+   * "public" addr returned is checked against ifconfig for
+   * possible clash: non sense for now given
+   * that we do ifconfig only IPv4
+   */
+#if defined(USE_PF_INET6) 
+  if(addr->addr.sa.sa_family != AF_INET)
+    return 0;
+#else 
+  ASSERT(addr->addr.sa.sa_family == AF_INET);
+#endif
+  return ntohl (addr->addr.in4.sin_addr.s_addr);
 }
 
 static inline bool
 addr_port_match (const struct openvpn_sockaddr *a1, const struct openvpn_sockaddr *a2)
 {
-  return a1->sa.sin_addr.s_addr == a2->sa.sin_addr.s_addr
-    && a1->sa.sin_port == a2->sa.sin_port;
+  switch(a1->addr.sa.sa_family) {
+    case AF_INET:
+      return a1->addr.in4.sin_addr.s_addr == a2->addr.in4.sin_addr.s_addr
+	&& a1->addr.in4.sin_port == a2->addr.in4.sin_port;
+#ifdef USE_PF_INET6
+    case AF_INET6:
+      return IN6_ARE_ADDR_EQUAL(&a1->addr.in6.sin6_addr, &a2->addr.in6.sin6_addr) 
+	&& a1->addr.in6.sin6_port == a2->addr.in6.sin6_port;
+#endif
+  }
+  ASSERT(0);
+  return false;
 }
 
 static inline bool
@@ -571,6 +681,74 @@
     : addr_port_match (a1, a2);
 }
 
+static inline void
+addr_zero_host(struct openvpn_sockaddr *addr)
+{
+   switch(addr->addr.sa.sa_family) {
+     case AF_INET:
+       addr->addr.in4.sin_addr.s_addr = 0;
+       break;
+#ifdef USE_PF_INET6
+     case AF_INET6: 
+       memset(&addr->addr.in6.sin6_addr, 0, sizeof (struct in6_addr));
+       break;
+#endif
+   }
+}
+
+static inline void
+addr_copy_sa(struct openvpn_sockaddr *dst, const struct openvpn_sockaddr *src)
+{
+  dst->addr = src->addr;
+}
+
+static inline void
+addr_copy_host(struct openvpn_sockaddr *dst, const struct openvpn_sockaddr *src)
+{
+   switch(src->addr.sa.sa_family) {
+     case AF_INET:
+       dst->addr.in4.sin_addr.s_addr = src->addr.in4.sin_addr.s_addr;
+       break;
+#ifdef USE_PF_INET6
+     case AF_INET6: 
+       dst->addr.in6.sin6_addr = src->addr.in6.sin6_addr;
+       break;
+#endif
+   }
+}
+
+static inline bool
+addr_inet4or6(struct sockaddr *addr)
+{
+	return addr->sa_family == AF_INET || addr->sa_family == AF_INET6;
+}
+
+int addr_guess_family(int proto, const char *name);
+static inline int
+af_addr_size(unsigned short af)
+{
+#if defined(USE_PF_INET6) || defined (USE_PF_UNIX)
+   switch(af) {
+     case AF_INET: return sizeof (struct sockaddr_in);
+#ifdef USE_PF_UNIX
+     case AF_UNIX: return sizeof (struct sockaddr_un);
+#endif
+#ifdef USE_PF_INET6
+     case AF_INET6: return sizeof (struct sockaddr_in6);
+#endif
+     default: 
+#if 0
+      /* could be called from socket_do_accept() with empty addr */
+      msg (M_ERR, "Bad address family: %d\n", af);
+      ASSERT(0);
+#endif
+     	return 0;
+   }
+#else /* only AF_INET */
+   return sizeof(struct sockaddr_in);
+#endif
+}
+
 static inline bool
 link_socket_actual_match (const struct link_socket_actual *a1, const struct link_socket_actual *a2)
 {
@@ -627,14 +805,18 @@
 {
   if (buf->len > 0)
     {
-      if (from_addr->dest.sa.sin_family != AF_INET)
-	return false;
-      if (!link_socket_actual_defined (from_addr))
-	return false;
-      if (info->remote_float || !addr_defined (&info->lsa->remote))
-	return true;
-      if (addr_match_proto (&from_addr->dest, &info->lsa->remote, info->proto))
-	return true;
+      switch (from_addr->dest.addr.sa.sa_family) {
+#ifdef USE_PF_INET6
+	case AF_INET6:
+#endif
+	case AF_INET:
+	  if (!link_socket_actual_defined (from_addr))
+	    return false;
+	  if (info->remote_float || !addr_defined (&info->lsa->remote))
+	    return true;
+	  if (addr_match_proto (&from_addr->dest, &info->lsa->remote, info->proto))
+	    return true;
+      }
     }
   return false;
 }
@@ -740,7 +922,7 @@
 		  int maxsize,
 		  struct link_socket_actual *from)
 {
-  if (sock->info.proto == PROTO_UDPv4)
+  if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
     {
       int res;
 
@@ -751,10 +933,10 @@
 #endif
       return res;
     }
-  else if (sock->info.proto == PROTO_TCPv4_SERVER || sock->info.proto == PROTO_TCPv4_CLIENT)
+  else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */
     {
       /* from address was returned by accept */
-      from->dest.sa = sock->info.lsa->actual.dest.sa;
+      addr_copy_sa(&from->dest, &sock->info.lsa->actual.dest);
       return link_socket_read_tcp (sock, buf);
     }
   else
@@ -809,13 +991,14 @@
 					   struct buffer *buf,
 					   struct link_socket_actual *to);
 
-  if (sock->sockflags & SF_USE_IP_PKTINFO)
+  if (proto_is_udp(sock->info.proto) && (sock->sockflags & SF_USE_IP_PKTINFO)
+	  && addr_defined_ipi(to))
     return link_socket_write_udp_posix_sendmsg (sock, buf, to);
   else
 #endif
     return sendto (sock->sd, BPTR (buf), BLEN (buf), 0,
-		   (struct sockaddr *) &to->dest.sa,
-		   (socklen_t) sizeof (to->dest.sa));
+		   (struct sockaddr *) &to->dest.addr.sa,
+		   (socklen_t) af_addr_size(to->dest.addr.sa.sa_family));
 }
 
 static inline int
@@ -846,11 +1029,11 @@
 		   struct buffer *buf,
 		   struct link_socket_actual *to)
 {
-  if (sock->info.proto == PROTO_UDPv4)
+  if (proto_is_udp(sock->info.proto)) /* unified UDPv4 and UDPv6 */
     {
       return link_socket_write_udp (sock, buf, to);
     }
-  else if (sock->info.proto == PROTO_TCPv4_SERVER || sock->info.proto == PROTO_TCPv4_CLIENT)
+  else if (proto_is_tcp(sock->info.proto)) /* unified TCPv4 and TCPv6 */
     {
       return link_socket_write_tcp (sock, buf, to);
     }
--- socks.c
+++ socks.c
@@ -299,9 +299,9 @@
 
   if (addr != NULL)
     {
-      addr->sa.sin_family = AF_INET;
-      addr->sa.sin_addr.s_addr = htonl (INADDR_ANY);
-      addr->sa.sin_port = htons (0);
+      addr->addr.in4.sin_family = AF_INET;
+      addr->addr.in4.sin_addr.s_addr = htonl (INADDR_ANY);
+      addr->addr.in4.sin_port = htons (0);
     }
 
   while (len < 4 + alen + 2)
@@ -388,8 +388,8 @@
   /* ATYP == 1 (IP V4 address) */
   if (atyp == '\x01' && addr != NULL)
     {
-      memcpy (&addr->sa.sin_addr, buf + 4, sizeof (addr->sa.sin_addr));
-      memcpy (&addr->sa.sin_port, buf + 8, sizeof (addr->sa.sin_port));
+      memcpy (&addr->addr.in4.sin_addr, buf + 4, sizeof (addr->addr.in4.sin_addr));
+      memcpy (&addr->addr.in4.sin_port, buf + 8, sizeof (addr->addr.in4.sin_port));
     }
 
 
@@ -507,8 +507,8 @@
   if (atyp != 1)		/* ATYP == 1 (IP V4) */
     goto error;
 
-  buf_read (buf, &from->dest.sa.sin_addr, sizeof (from->dest.sa.sin_addr));
-  buf_read (buf, &from->dest.sa.sin_port, sizeof (from->dest.sa.sin_port));
+  buf_read (buf, &from->dest.addr.in4.sin_addr, sizeof (from->dest.addr.in4.sin_addr));
+  buf_read (buf, &from->dest.addr.in4.sin_port, sizeof (from->dest.addr.in4.sin_port));
 
   return;
 
@@ -540,8 +540,8 @@
   buf_write_u16 (&head, 0);	/* RSV = 0 */
   buf_write_u8 (&head, 0);	/* FRAG = 0 */
   buf_write_u8 (&head, '\x01'); /* ATYP = 1 (IP V4) */
-  buf_write (&head, &to->dest.sa.sin_addr, sizeof (to->dest.sa.sin_addr));
-  buf_write (&head, &to->dest.sa.sin_port, sizeof (to->dest.sa.sin_port));
+  buf_write (&head, &to->dest.addr.in4.sin_addr, sizeof (to->dest.addr.in4.sin_addr));
+  buf_write (&head, &to->dest.addr.in4.sin_port, sizeof (to->dest.addr.in4.sin_port));
 
   return 10;
 }
--- syshead.h
+++ syshead.h
@@ -28,6 +28,10 @@
 /*
  * Only include if not during configure
  */
+#ifdef WIN32
+/* USE_PF_INET6: win32 ipv6 exists only after 0x0501 (XP) */
+#define WINVER 0x0501
+#endif
 #ifndef PACKAGE_NAME
 #include "config.h"
 #endif
@@ -339,6 +343,9 @@
 #ifdef WIN32
 #include <iphlpapi.h>
 #include <wininet.h>
+/* The following two headers are needed of USE_PF_INET6 */
+#include <winsock2.h>
+#include <ws2tcpip.h>
 #endif
 
 #ifdef HAVE_SYS_MMAN_H
@@ -383,9 +390,10 @@
 #endif
 
 /*
- * Does this platform support linux-style IP_PKTINFO?
+ * Does this platform support linux-style IP_PKTINFO
+ * or bsd-style IP_RECVDSTADDR ?
  */
-#if defined(ENABLE_MULTIHOME) && defined(HAVE_IN_PKTINFO) && defined(IP_PKTINFO) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)
+#if defined(ENABLE_MULTIHOME) && ((defined(HAVE_IN_PKTINFO)&&defined(IP_PKTINFO)) || defined(IP_RECVDSTADDR)) && defined(HAVE_MSGHDR) && defined(HAVE_CMSGHDR) && defined(HAVE_IOVEC) && defined(CMSG_FIRSTHDR) && defined(CMSG_NXTHDR) && defined(HAVE_RECVMSG) && defined(HAVE_SENDMSG)
 #define ENABLE_IP_PKTINFO 1
 #else
 #define ENABLE_IP_PKTINFO 0
--- TODO.ipv6
+++ TODO.ipv6
@@ -0,0 +1,30 @@
+[ Last updated: 11-Nov-2009. ]
+
+* All platforms:
+  o mgmt console: as currently passes straight in_addr_t bits around
+
+  o make possible to get AF from getaddrinfo() answer, ie allow openvpn to
+    use ipv4/6 if DNS returns A/AAAA without specifying protocol.
+    Hard: requires deep changes in initialization/calling logic
+
+  o use AI_PASSIVE
+
+  o the getaddr()/getaddr6() interface is not prepared for handling socktype
+    "tagging", currently I abuse the sockflags bits for getting the ai_socktype
+    downstream.
+
+  o implement comparison for mapped addesses: server in dual stack
+    listening IPv6 must permit incoming streams from allowed IPv4 peer,
+    currently you need to pass eg:  --remote ffff::1.2.3.4
+
+  o do something with multi mode learn routes, for now just ignoring
+    ipv6 addresses seems the most sensible thing to do, because there's
+    no support for intra-tunnel ipv6 stuff.
+
+* win32:
+  o find out about mapped addresses, as I can't make it work
+    with bound at ::1 and connect to 127.0.0.1
+
+* N/A:
+  o this is ipv6 *endpoint* support, so don't expect "ifconfig6"-like
+    support in this patch 
--- tun.c
+++ tun.c
@@ -1688,7 +1688,9 @@
 	  strerror(errno));
       }
 
+#ifdef IFF_MULTICAST /* openbsd 4.x doesn't have this */
       info.flags |= IFF_MULTICAST;
+#endif
 
       if (ioctl (tt->fd, TUNSIFINFO, &info) < 0) {
 	msg (M_WARN | M_ERRNO, "Can't set interface info: %s",
--- win32.h
+++ win32.h
@@ -195,7 +195,10 @@
   DWORD flags;
   int status;
   bool addr_defined;
-  struct sockaddr_in addr;
+  union {
+    struct sockaddr_in addr;
+    struct sockaddr_in6 addr6;
+  };
   int addrlen;
   struct buffer buf_init;
   struct buffer buf;
