Message ID | 20230105022718.1641751-1-selva.nair@gmail.com |
---|---|
State | Accepted |
Headers | show |
Series | [Openvpn-devel,1/3] Use IPAPI for setting ipv6 routes when iservice not available | expand |
Hi,
Compiled and slightly tested - pinged server's tunnel IPv6 address.
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Hi,
On Fri, Jan 06, 2023 at 03:44:24PM +0200, Lev Stipakov wrote:
> Compiled and slightly tested - pinged server's tunnel IPv6 address.
Do we call "route" for connect networks on Windows? We do on some
of the platforms, but for those where we do not, this is not a good
test for "route add" functionality - as tunnel IP + subnet gets
setup via the "ifconfig-ipv6" part (implicit route for connected net).
gert
On Fri, Jan 6, 2023 at 3:19 PM Gert Doering <gert@greenie.muc.de> wrote: > Hi, > > On Fri, Jan 06, 2023 at 03:44:24PM +0200, Lev Stipakov wrote: > > Compiled and slightly tested - pinged server's tunnel IPv6 address. > > Do we call "route" for connect networks on Windows? We do on some > of the platforms, but for those where we do not, this is not a good > test for "route add" functionality - as tunnel IP + subnet gets > setup via the "ifconfig-ipv6" part (implicit route for connected net). > Yes we do call add_route_connected_v6_net() on windows, so add_route_ipv6() should always get exercised. Though I vaguely understand why we set the address with /128 and then set the route for the vpn subnet with fe80::8 as the gateway for tap-windows6, wonder why we do this for dco as well. Selva
Hi, On Fri, Jan 06, 2023 at 04:46:17PM -0500, Selva Nair wrote: > > On Fri, Jan 06, 2023 at 03:44:24PM +0200, Lev Stipakov wrote: > > > Compiled and slightly tested - pinged server's tunnel IPv6 address. > > > > Do we call "route" for connect networks on Windows? We do on some > > of the platforms, but for those where we do not, this is not a good > > test for "route add" functionality - as tunnel IP + subnet gets > > setup via the "ifconfig-ipv6" part (implicit route for connected net). > > Yes we do call add_route_connected_v6_net() on windows, so add_route_ipv6() > should always get exercised. Thanks for reminding me :-) > Though I vaguely understand why we set the address with /128 and then set > the route for the vpn subnet with fe80::8 as the gateway for tap-windows6, > wonder why we do this for dco as well. Let me recap why we do this for tap-windows - for windows, this is always an Ethernet adapter, so it wants to always do neighbour discovery (ND), IPv6's equivalent of ARP for all "on this interface" addresses. For "--dev tun" setups, OpenVPN does not want to deal with "Ethernet things", so the TAP driver replies to ARP requests for "special addresses" - for net30, OpenVPN ioctl()'s the "peer" address (which is also the gateway address) into the TAP driver, and only this address is replied to - admittedly, I do not know how "topology subnet" works wrt virtual ARP, need to read up on this again. For IPv6, I decided that having to deal with ND is easier if I could just look at a magic number, namely fe80::8, in the tap driver code - so, we use /128 for the ifconfig bit ("no network here, everything is behind the gateway") and point all routing to "on this interface, fe80::8" - so for whatever IPv6 destination, windows will only do ND for fe80::8, and the TAP driver will reply to it, without any further help (ioctl()) from OpenVPN. This said, I have no idea why we do it that way for wintun and DCO - most likely because it avoids having to add even more windows-driver- specific special case handling in places where we are nicely generic today. We are not really losing anything this way, though - even though we pretend "the interface is a multiaccess interface", it is really only point-to-point from the OS view, including win-dco. So whatever helps to convince windows "send the packet down this pipe" with the least amount of code and debugging is good :-) On a "real" multiaccess network, different neighbours would have different MAC addresses, and more ND activity would happen - but "--dev tun" mode never is (--dev tap on a --server would be). gert
On Sat, Jan 7, 2023 at 11:12 AM Gert Doering <gert@greenie.muc.de> wrote: > > > > Though I vaguely understand why we set the address with /128 and then set > > the route for the vpn subnet with fe80::8 as the gateway for > tap-windows6, > > wonder why we do this for dco as well. > > Let me recap why we do this for tap-windows - for windows, this is always > an Ethernet adapter, so it wants to always do neighbour discovery (ND), > IPv6's equivalent of ARP for all "on this interface" addresses. > > For "--dev tun" setups, OpenVPN does not want to deal with "Ethernet > things", > so the TAP driver replies to ARP requests for "special addresses" - for > net30, OpenVPN ioctl()'s the "peer" address (which is also the gateway > address) into the TAP driver, and only this address is replied to - > admittedly, I do not know how "topology subnet" works wrt virtual ARP, > need to read up on this again. > > For IPv6, I decided that having to deal with ND is easier if I could just > look at a magic number, namely fe80::8, in the tap driver code - so, we > use /128 for the ifconfig bit ("no network here, everything is behind the > gateway") and point all routing to "on this interface, fe80::8" - so for > whatever IPv6 destination, windows will only do ND for fe80::8, and the > TAP driver will reply to it, without any further help (ioctl()) from > OpenVPN. > Thanks for the explanation. I had a vague idea that this is done for ND. > > > This said, I have no idea why we do it that way for wintun and DCO - > most likely because it avoids having to add even more windows-driver- > specific special case handling in places where we are nicely generic > today. > I guess it works because nexthop is ignored[*] for tun adapters and we could have just set an onlink route omitting the gateway parameter. Selva [*] Any bogus gateway seems to work, but still, "netsh int ipv6 show neighbours" lists any such gateway set for dco as initially "Probe" and then "Unreachable", so not totally ignored?
Stared-at-code ("looks reasonable"). Getting rid of netsh.exe calls is a good thing. Actually tested! MinGW builds, on a Win10 system, running openvpn.exe from a "cmd.exe run as administrator" window, so no iservice involved - OpenVPN tells me "IPv6 route added using ipapi", and "route print -6" looks good. There is something incorrect in the log, though - I connect via IPv6, and OpenVPN needs to install a redirect host route due to "gateway and pushed route overlaps". It is installed correctly, that is, the /128 route points to the LAN interface and that IPv6 default route (fe80::1), but the log says add_route_ipv6(2001:608:8003::200/128 -> fe80::1 metric 1) dev LAN-Verbindung 2 IPv6 route added using ipapi add_route_ipv6(2001:608:8003::/48 -> 2001:608:8003:f:98::1 metric -1) dev LAN-Verbindung 2 IPv6 route added using ipapi In "route show -6", the first route ends up on "If 8 ... Intel Pro/100" and the second route on "If 2 ... TAP-Windows Adapter V9 #2" - which is both correct, but only the second is "dev LAN-Verbindung 2". So it seems to always print the name of the TAP adapter here - which is misleading at best. I have also tested "not run as administrator", and it will correctly error out with "ROUTE: route addition failed using ipapi: Zugriff verweigert [status=5 if_index=2]" (if it gets there at all :-) - without --ifconfig-noexec, it fails IPv6 ifconfig/netsh already, and never proceeds to route addition - which actually brings up the observation that there's netsh.exe calls left, "netsh.exe interface ipv6 set address 2 2001:608:..." :-) ) Your patch has been applied to the master and release/2.6 branch. commit dd66958198f7c4dcf7fca0db82ca72996100b3bd (master) commit 66a3dc3a007c13e7a8d48ef793e046b09d8e6d30 (release/2.6) Author: Selva Nair Date: Wed Jan 4 21:27:16 2023 -0500 Use IPAPI for setting ipv6 routes when iservice not available Signed-off-by: Selva Nair <selva.nair@gmail.com> Acked-by: Lev Stipakov <lstipakov@gmail.com> Message-Id: <20230105022718.1641751-1-selva.nair@gmail.com> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg25886.html Signed-off-by: Gert Doering <gert@greenie.muc.de> -- kind regards, Gert Doering
Hi which actually brings up the observation that there's netsh.exe calls > left, "netsh.exe interface ipv6 set address 2 2001:608:..." :-) ) > Yes, it's on my radar :) Selva
diff --git a/src/openvpn/route.c b/src/openvpn/route.c index ded8fec8..eabfe0a5 100644 --- a/src/openvpn/route.c +++ b/src/openvpn/route.c @@ -65,6 +65,8 @@ static bool add_route_ipv6_service(const struct route_ipv6 *, const struct tunta static bool del_route_ipv6_service(const struct route_ipv6 *, const struct tuntap *); +static bool route_ipv6_ipapi(bool add, const struct route_ipv6 *, const struct tuntap *); + #endif static void delete_route(struct route_ipv4 *r, const struct tuntap *tt, unsigned int flags, const struct route_gateway_info *rgi, const struct env_set *es, openvpn_net_ctx_t *ctx); @@ -1975,58 +1977,8 @@ add_route_ipv6(struct route_ipv6 *r6, const struct tuntap *tt, } else { - DWORD adapter_index; - if (r6->adapter_index) /* vpn server special route */ - { - adapter_index = r6->adapter_index; - gateway_needed = true; - } - else - { - adapter_index = tt->adapter_index; - } - - /* netsh interface ipv6 add route 2001:db8::/32 42 */ - argv_printf(&argv, "%s%s interface ipv6 add route %s/%d %lu", - get_win_sys_path(), - NETSH_PATH_SUFFIX, - network, - r6->netbits, - adapter_index); - - /* 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 && !gateway_needed) - { - argv_printf_cat( &argv, " %s", "fe80::8" ); - } - else if (!IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) ) - { - argv_printf_cat( &argv, " %s", gateway ); - } - -#if 0 - if (r6->flags & RT_METRIC_DEFINED) - { - argv_printf_cat(&argv, " METRIC %d", r->metric); - } -#endif - - /* in some versions of Windows, routes are persistent across reboots by - * default, unless "store=active" is set (pointed out by Tony Lim, thanks) - */ - argv_printf_cat( &argv, " store=active" ); - - 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(); + status = route_ipv6_ipapi(true, r6, tt); } - #elif defined (TARGET_SOLARIS) /* example: route add -inet6 2001:db8::/32 somegateway 0 */ @@ -2416,60 +2368,8 @@ delete_route_ipv6(const struct route_ipv6 *r6, const struct tuntap *tt, } else { - DWORD adapter_index; - if (r6->adapter_index) /* vpn server special route */ - { - adapter_index = r6->adapter_index; - gateway_needed = true; - } - else - { - adapter_index = tt->adapter_index; - } - - /* netsh interface ipv6 delete route 2001:db8::/32 42 */ - argv_printf(&argv, "%s%s interface ipv6 delete route %s/%d %lu", - get_win_sys_path(), - NETSH_PATH_SUFFIX, - network, - r6->netbits, - adapter_index); - - /* 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 && !gateway_needed) - { - argv_printf_cat( &argv, " %s", "fe80::8" ); - } - else if (!IN6_IS_ADDR_UNSPECIFIED(&r6->gateway) ) - { - argv_printf_cat( &argv, " %s", gateway ); - } - -#if 0 - if (r6->flags & RT_METRIC_DEFINED) - { - argv_printf_cat(&argv, "METRIC %d", r->metric); - } -#endif - - /* Windows XP to 7 "just delete" routes, wherever they came from, but - * in Windows 8(.1?), if you create them with "store=active", this is - * how you should delete them as well (pointed out by Cedric Tabary) - */ - argv_printf_cat( &argv, " store=active" ); - - argv_msg(D_ROUTE, &argv); - - netcmd_semaphore_lock(); - openvpn_execve_check(&argv, es, 0, "ERROR: Windows route delete ipv6 command failed"); - netcmd_semaphore_release(); + route_ipv6_ipapi(false, r6, tt); } - #elif defined (TARGET_SOLARIS) /* example: route delete -inet6 2001:db8::/32 somegateway */ @@ -3049,6 +2949,73 @@ do_route_ipv4_service(const bool add, const struct route_ipv4 *r, const struct t return do_route_service(add, &msg, sizeof(msg), tt->options.msg_channel); } +/* Add or delete an ipv6 route */ +static bool +route_ipv6_ipapi(const bool add, const struct route_ipv6 *r, const struct tuntap *tt) +{ + DWORD err; + PMIB_IPFORWARD_ROW2 fwd_row; + struct gc_arena gc = gc_new(); + + fwd_row = gc_malloc(sizeof(*fwd_row), true, &gc); + + fwd_row->ValidLifetime = 0xffffffff; + fwd_row->PreferredLifetime = 0xffffffff; + fwd_row->Protocol = MIB_IPPROTO_NETMGMT; + fwd_row->Metric = ((r->flags & RT_METRIC_DEFINED) ? r->metric : -1); + fwd_row->DestinationPrefix.Prefix.si_family = AF_INET6; + fwd_row->DestinationPrefix.Prefix.Ipv6.sin6_addr = r->network; + fwd_row->DestinationPrefix.PrefixLength = (UINT8) r->netbits; + fwd_row->NextHop.si_family = AF_INET6; + fwd_row->NextHop.Ipv6.sin6_addr = r->gateway; + fwd_row->InterfaceIndex = r->adapter_index ? r->adapter_index : tt->adapter_index; + + /* In TUN mode we use a special link-local address as the next hop. + * The tapdrvr knows about it and will answer neighbor discovery packets. + * (only do this for routes actually using the tun/tap device) + */ + if (tt->type == DEV_TYPE_TUN && !r->adapter_index) + { + inet_pton(AF_INET6, "fe80::8", &fwd_row->NextHop.Ipv6.sin6_addr); + } + + /* Use LUID if interface index not available */ + if (fwd_row->InterfaceIndex == TUN_ADAPTER_INDEX_INVALID && strlen(tt->actual_name)) + { + NET_LUID luid; + err = ConvertInterfaceAliasToLuid(wide_string(tt->actual_name, &gc), &luid); + if (err != NO_ERROR) + { + goto out; + } + fwd_row->InterfaceLuid = luid; + fwd_row->InterfaceIndex = 0; + } + + if (add) + { + err = CreateIpForwardEntry2(fwd_row); + } + else + { + err = DeleteIpForwardEntry2(fwd_row); + } + +out: + if (err != NO_ERROR) + { + msg(M_WARN, "ROUTE: route %s failed using ipapi: %s [status=%lu if_index=%lu]", + (add ? "addition" : "deletion"), strerror_win32(err, &gc), err, fwd_row->InterfaceIndex); + } + else + { + msg(D_ROUTE, "IPv6 route %s using ipapi", add ? "added" : "deleted"); + } + gc_free(&gc); + + return (err == NO_ERROR); +} + static bool do_route_ipv6_service(const bool add, const struct route_ipv6 *r, const struct tuntap *tt) {