* Duplicate IP address, and permissions problems on Windows @ 2021-04-06 11:29 David Woodhouse 2021-04-07 0:17 ` Jason A. Donenfeld 0 siblings, 1 reply; 11+ messages in thread From: David Woodhouse @ 2021-04-06 11:29 UTC (permalink / raw) To: WireGuard mailing list; +Cc: Daniel Lenski [-- Attachment #1: Type: text/plain, Size: 1685 bytes --] I'm looking at porting the Windows build of OpenConnect¹ to use Wintun instead of the OpenVPN TAP-Windows driver. The Wintun example.c shows how to add a Legacy IP address using CreateUnicastIpAddressEntry(), and I tried to do it that way. But it doesn't always work. When I reconnect to the VPN using Wintun after connecting to the same VPN using TAP-Windows, the Legacy IP address remains associated with the other adapter despite it being *down*. Even though CreateUnicastIpAddressEntry() returns success. I ended up writing something to iterate over the table returned by GetUnicastIpAddressTable() and *remove* the address from every other adapter: https://gitlab.com/openconnect/openconnect/-/commit/60d1f092e35#71552d5f529101dd4a1be98f9ac9afffc3c5c9d1_0_141 Is it expected that this is necessary? Do we have similar problems if the IP address is set by invoking 'netsh interface ip set address'? The second issue I have on Windows is permissions. With TAP-Windows the user had to install the adapter with elevated permissions, but could run the VPN client in their user account. Using Wintun that doesn't seem to work, and Administrator privileges are required. What is the "correct" way of handling that? Are the elevated privileges only needed to *create* the Wintun adapter, and can it be used without them after the driver is already loaded? This doesn't seem to be covered by the documentation or example code. -- dwmw2 ¹ OpenConnect is the open source client for the various proprietary SSL VPNs — Cisco AnyConnect, Juniper/Pulse Secure, GlobalProtect, F5, Fortinet, etc.: https://www.infradead.org/openconnect/ [-- Attachment #2: smime.p7s --] [-- Type: application/x-pkcs7-signature, Size: 5174 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-06 11:29 Duplicate IP address, and permissions problems on Windows David Woodhouse @ 2021-04-07 0:17 ` Jason A. Donenfeld 2021-04-07 8:18 ` David Woodhouse 2021-04-07 23:00 ` Daniel Lenski 0 siblings, 2 replies; 11+ messages in thread From: Jason A. Donenfeld @ 2021-04-07 0:17 UTC (permalink / raw) To: David Woodhouse; +Cc: WireGuard mailing list, Daniel Lenski Hey David, It's pretty typical behavior on Windows for IP addresses to be exclusive per interface. WireGuard for Windows does something similar: https://git.zx2c4.com/wireguard-windows/tree/tunnel/addressconfig.go#n22 With regards to permissions, you must be Local System, which is already the case if you're running inside a service. If you'd like to run as a mere Administrator process, you can steal a token with a technique like https://git.zx2c4.com/wireguard-tools/tree/src/ipc-uapi-windows.h#n14 or https://git.zx2c4.com/wireguard-windows/tree/elevate/doas.go#n30 Jason ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 0:17 ` Jason A. Donenfeld @ 2021-04-07 8:18 ` David Woodhouse 2021-04-07 23:05 ` Daniel Lenski 2021-04-07 23:00 ` Daniel Lenski 1 sibling, 1 reply; 11+ messages in thread From: David Woodhouse @ 2021-04-07 8:18 UTC (permalink / raw) To: Jason A. Donenfeld; +Cc: WireGuard mailing list, Daniel Lenski [-- Attachment #1: Type: text/plain, Size: 1159 bytes --] On Tue, 2021-04-06 at 18:17 -0600, Jason A. Donenfeld wrote: > It's pretty typical behavior on Windows for IP addresses to be > exclusive per interface. WireGuard for Windows does something similar: > https://git.zx2c4.com/wireguard-windows/tree/tunnel/addressconfig.go#n22 Thanks. That seems to run cleanupAddressesOnDisconnectedInterfaces() only if the original SetIPAddressesForFamily() call return ERROR_OBJECT_ALREADY_EXISTS, while I was fairly sure that in my case the CreateUnicastIpAddressEntry() call was succeeding. I'll go and experiment with it some more. > With regards to permissions, you must be Local System, which is > already the case if you're running inside a service. If you'd like to > run as a mere Administrator process, you can steal a token with a > technique like https://git.zx2c4.com/wireguard-tools/tree/src/ipc-uapi-windows.h#n14 > or https://git.zx2c4.com/wireguard-windows/tree/elevate/doas.go#n30 Great, thanks! Is there a list of precisely which operations require such privileges? Is it only *creating* an adapter? Or only if doing so requires the kernel driver to be loaded for the first time? [-- Attachment #2: smime.p7s --] [-- Type: application/x-pkcs7-signature, Size: 5174 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 8:18 ` David Woodhouse @ 2021-04-07 23:05 ` Daniel Lenski 2021-04-12 17:50 ` Jason A. Donenfeld 0 siblings, 1 reply; 11+ messages in thread From: Daniel Lenski @ 2021-04-07 23:05 UTC (permalink / raw) To: David Woodhouse; +Cc: Jason A. Donenfeld, WireGuard mailing list On Wed, Apr 7, 2021 at 1:18 AM David Woodhouse <dwmw2@infradead.org> wrote: > > On Tue, 2021-04-06 at 18:17 -0600, Jason A. Donenfeld wrote: > > With regards to permissions, you must be Local System, which is > > already the case if you're running inside a service. If you'd like to > > run as a mere Administrator process, you can steal a token with a > > technique like https://git.zx2c4.com/wireguard-tools/tree/src/ipc-uapi-windows.h#n14 > > or https://git.zx2c4.com/wireguard-windows/tree/elevate/doas.go#n30 > > Great, thanks! > > Is there a list of precisely which operations require such privileges? > Is it only *creating* an adapter? Or only if doing so requires the > kernel driver to be loaded for the first time? > I'm a little confused by this. In my testing of our recent builds of OpenConnect on Windows 2012 R2 with wintun-0.10.2… Running as Administrator *has been* sufficient to allow OpenConnect to open the Wintun adapters, as well as to configure them with "netsh", etc. Is there some additional environment we should be testing in, where Administrator may *not* be sufficient? Thanks, Dan ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 23:05 ` Daniel Lenski @ 2021-04-12 17:50 ` Jason A. Donenfeld 0 siblings, 0 replies; 11+ messages in thread From: Jason A. Donenfeld @ 2021-04-12 17:50 UTC (permalink / raw) To: Daniel Lenski; +Cc: David Woodhouse, WireGuard mailing list On Wed, Apr 7, 2021 at 5:05 PM Daniel Lenski <dlenski@gmail.com> wrote: > > On Wed, Apr 7, 2021 at 1:18 AM David Woodhouse <dwmw2@infradead.org> wrote: > > > > On Tue, 2021-04-06 at 18:17 -0600, Jason A. Donenfeld wrote: > > > With regards to permissions, you must be Local System, which is > > > already the case if you're running inside a service. If you'd like to > > > run as a mere Administrator process, you can steal a token with a > > > technique like https://git.zx2c4.com/wireguard-tools/tree/src/ipc-uapi-windows.h#n14 > > > or https://git.zx2c4.com/wireguard-windows/tree/elevate/doas.go#n30 > > > > Great, thanks! > > > > Is there a list of precisely which operations require such privileges? > > Is it only *creating* an adapter? Or only if doing so requires the > > kernel driver to be loaded for the first time? > > > > I'm a little confused by this. In my testing of our recent builds of > OpenConnect on Windows 2012 R2 with wintun-0.10.2… > > Running as Administrator *has been* sufficient to allow OpenConnect to > open the Wintun adapters, as well as to configure them with "netsh", > etc. > > Is there some additional environment we should be testing in, where > Administrator may *not* be sufficient? Oh, sorry, you're right. Administrator _is_ sufficient for this, because the code I mentioned above to do automatic elevation is part of wintun.dll. Sorry for the confusion. Jason ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 0:17 ` Jason A. Donenfeld 2021-04-07 8:18 ` David Woodhouse @ 2021-04-07 23:00 ` Daniel Lenski 2021-04-08 8:46 ` David Woodhouse 2021-04-08 16:59 ` David Woodhouse 1 sibling, 2 replies; 11+ messages in thread From: Daniel Lenski @ 2021-04-07 23:00 UTC (permalink / raw) To: Jason A. Donenfeld; +Cc: David Woodhouse, WireGuard mailing list On Tue, Apr 6, 2021 at 5:17 PM Jason A. Donenfeld <Jason@zx2c4.com> wrote: > It's pretty typical behavior on Windows for IP addresses to be > exclusive per interface. WireGuard for Windows does something similar: > https://git.zx2c4.com/wireguard-windows/tree/tunnel/addressconfig.go#n22 Thank you! That's very interesting. Following David's initial implementation, I wrote something for OpenConnect that's pretty much the same as yours: https://gitlab.com/openconnect/openconnect/-/compare/5e6e9b850756157164f83cd4fedafb747fbbd50f...0bca5b32ac478b5d03b6e88f96bf29c6556610a5 1. Uses GetAdaptersAddresses to list all the addresses 2. If/when it finds a clashing address, it uses GetUnicastIpAddressTable to determine the up/down state of the other interface 3. Only delete the address from the other interface if it's non-UP. I was also annoyed that the GetAdaptersAddresses return structure doesn't provide the adapter state, and that I had to go for this convoluted O(n^2) design. I guess this reassures me that there isn't an obviously-better way to do it. Dan ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 23:00 ` Daniel Lenski @ 2021-04-08 8:46 ` David Woodhouse 2021-04-08 16:09 ` Daniel Lenski 2021-04-08 16:59 ` David Woodhouse 1 sibling, 1 reply; 11+ messages in thread From: David Woodhouse @ 2021-04-08 8:46 UTC (permalink / raw) To: Daniel Lenski, Jason A. Donenfeld; +Cc: WireGuard mailing list On 8 April 2021 00:00:46 BST, Daniel Lenski <dlenski@gmail.com> wrote: >On Tue, Apr 6, 2021 at 5:17 PM Jason A. Donenfeld <Jason@zx2c4.com> >wrote: >> It's pretty typical behavior on Windows for IP addresses to be >> exclusive per interface. WireGuard for Windows does something >similar: >> >https://git.zx2c4.com/wireguard-windows/tree/tunnel/addressconfig.go#n22 > >Thank you! That's very interesting. > >Following David's initial implementation, I wrote something for >OpenConnect that's pretty much the same as yours: >https://gitlab.com/openconnect/openconnect/-/compare/5e6e9b850756157164f83cd4fedafb747fbbd50f...0bca5b32ac478b5d03b6e88f96bf29c6556610a5 > >1. Uses GetAdaptersAddresses to list all the addresses >2. If/when it finds a clashing address, it uses >GetUnicastIpAddressTable to determine the up/down state of the other >interface >3. Only delete the address from the other interface if it's non-UP. > >I was also annoyed that the GetAdaptersAddresses return structure >doesn't provide the adapter state, and that I had to go for this >convoluted O(n^2) design. > >I guess this reassures me that there isn't an obviously-better way to >do it. Unless netsh will do it for us when we ask *it* to set the IP address? OpenConnect doesn't normally bother itself with administrivia like setting IP addresses; its job is to pass packets. When I first wrote it, I just usurped the vpnc-script from vpnc which does all the routing/DNS/etc configuration for every platform under the sun, so all we do generally in OpenConnect is set the environment variables up and spawn the script. The only reason we ever set a Legacy IP directly in C for Tap-Windows was to make it do all the fake ARP nonsense correctly. (And ISTR we didn't need to do anything for IPv6 as it just needs to use a known lladdr as the route gw). We don't need that with Wintun. I preserved it in the first cut of Wintun support because the existing vpnc-script for Windows actually depends on it... but purely for waiting for the interface to come up. Can we ditch that, let the script set the address for us, and forget we ever saw that O(n²) code because netsh handles the conflicting interfaces for us? -- Sent from my Android device with K-9 Mail. Please excuse my brevity. ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-08 8:46 ` David Woodhouse @ 2021-04-08 16:09 ` Daniel Lenski 0 siblings, 0 replies; 11+ messages in thread From: Daniel Lenski @ 2021-04-08 16:09 UTC (permalink / raw) To: David Woodhouse; +Cc: Jason A. Donenfeld, WireGuard mailing list On Thu, Apr 8, 2021 at 1:46 AM David Woodhouse <dwmw2@infradead.org> wrote: > Unless netsh will do it for us when we ask *it* to set the IP address? OpenConnect doesn't normally bother itself with administrivia like setting IP addresses; its job is to pass packets. I'm afraid not. I tried playing around with various documented and undocumented options to netsh, and did not found a way to get it to set an IP address which is a duplicate of another adapter's address. That's puzzling since it IS POSSIBLE to set a duplicate IP address (conflicting with a down adapter) via the Control Panel GUI. I gave up and asked for any hints on StackExchange: https://serverfault.com/questions/1059221/using-netsh-interface-ip-set-address-to-override-another-adapters-address > I preserved it in the first cut of Wintun support because the existing vpnc-script for Windows actually depends on it... but purely for waiting for the interface to come up. Can we ditch that, let the script set the address for us, and forget we ever saw that O(n²) code because netsh handles the conflicting interfaces for us? One way we could move this IP-reclaiming code out of OpenConnect itself would be to make the script do it. However, this would still be an O(n^2) loop, and it would involve parsing the text output of netsh or similar… which will get pretty ugly very fast. (Either that or someone figures out a/the magical option that allows netsh to handle this itself.) ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-07 23:00 ` Daniel Lenski 2021-04-08 8:46 ` David Woodhouse @ 2021-04-08 16:59 ` David Woodhouse 2021-04-08 17:53 ` Daniel Lenski 1 sibling, 1 reply; 11+ messages in thread From: David Woodhouse @ 2021-04-08 16:59 UTC (permalink / raw) To: Daniel Lenski, Jason A. Donenfeld; +Cc: WireGuard mailing list [-- Attachment #1: Type: text/plain, Size: 2420 bytes --] On Wed, 2021-04-07 at 16:00 -0700, Daniel Lenski wrote: > Following David's initial implementation, I wrote something for > OpenConnect that's pretty much the same as yours: > https://gitlab.com/openconnect/openconnect/-/compare/5e6e9b850756157164f83cd4fedafb747fbbd50f...0bca5b32ac478b5d03b6e88f96bf29c6556610a5 > > 1. Uses GetAdaptersAddresses to list all the addresses > 2. If/when it finds a clashing address, it uses > GetUnicastIpAddressTable to determine the up/down state of the other > interface > 3. Only delete the address from the other interface if it's non-UP. > > I was also annoyed that the GetAdaptersAddresses return structure > doesn't provide the adapter state, and that I had to go for this > convoluted O(n^2) design. > > I guess this reassures me that there isn't an obviously-better way to > do it. Hm, your description doesn't match the code I see at that link. You're using GetAdaptersAddresses() which gives you the UP/DOWN status as well as the addresses, and you iterate over those. The loop is ∀ adapter, ∀ Unicast address on that adapter: Check if it's our Legacy IP or IPv6 address. That isn't O(n²), is it? It's still O(n) of the total number of unicast addresses in the system? Once you've found an address which needs to be removed, you're *then* using GetUnicastIpAddressTable() and searching through the results to find the appropriate MIB_UNICASTIPADDRESS_ROW that you need to pass to DeleteUnicastIpAddressEntry(). Does *every* field in the MIB_UNICASTIPADDRESS_ROW have to be filled in, or is it just the Address, InterfaceLuid and InterfaceIndex? Can't we get those from the table we get back from GetAdaptersAddresses()? Alternatively, can't we start with GetUnicastIpAddressTable() as my original code did, and if we want to check whether an interface is down before we steal the address from it, use GetIfEntry2() to find out? Using GetIfEntry2() is probably a saner way to find the InterfaceIndex for the Wintun itself, which I was dredging the registry for manually. I'd like to be consistent about clearing the 'conflicting' addresses and setting the address on the Wintun interface. Whatever we do in OpenConnect for Legacy IP we should also do for IPv6. It looks like you're clearing the conflicting addresses for both families, but we still aren't *setting* the IPv6 address from the C code? [-- Attachment #2: smime.p7s --] [-- Type: application/x-pkcs7-signature, Size: 5174 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-08 16:59 ` David Woodhouse @ 2021-04-08 17:53 ` Daniel Lenski 2021-04-10 9:25 ` David Woodhouse 0 siblings, 1 reply; 11+ messages in thread From: Daniel Lenski @ 2021-04-08 17:53 UTC (permalink / raw) To: David Woodhouse; +Cc: Jason A. Donenfeld, WireGuard mailing list On Thu, Apr 8, 2021 at 9:59 AM David Woodhouse <dwmw2@infradead.org> wrote: > Hm, your description doesn't match the code I see at that link. > > You're using GetAdaptersAddresses() which gives you the UP/DOWN status > as well as the addresses, and you iterate over those. The loop is > > ∀ adapter, ∀ Unicast address on that adapter: > Check if it's our Legacy IP or IPv6 address. > > That isn't O(n²), is it? It's still O(n) of the total number of unicast > addresses in the system? It's O(n²) the number of unicast addresses, because there's an extra layer… ∀ adapter, ∀ Unicast address on that adapter (iterating via GetAdaptersAddresses) 1. Check if it's using our Legacy IP or IPv6 address. 2. If yes, then check if the other adapter is UP or non-UP 3. If non-UP, then… ∀ Unicast address on the system (iterating via GetUnicastIpAddressTable(), since the other one maddeningly lacks an API to delete addresses) 2. If non-UP, then steal/delete/reclaim the desired address from it. > Once you've found an address which needs to be removed, you're *then* > using GetUnicastIpAddressTable() and searching through the results to > find the appropriate MIB_UNICASTIPADDRESS_ROW that you need to pass to > DeleteUnicastIpAddressEntry(). > > Does *every* field in the MIB_UNICASTIPADDRESS_ROW have to be filled > in, or is it just the Address, InterfaceLuid and InterfaceIndex? Can't > we get those from the table we get back from GetAdaptersAddresses()? Yes, I already tried precisely this in https://gitlab.com/openconnect/openconnect/-/commit/b3dbabda7b68cf86fc72e2d5158b0707f74d61f0, and it doesn't work. I could faff around with it more, but even if I got it to work, it's clearly not how Microsoft wants us to do it, and liable to break. /* Create a "fake" MIB_UNICASTIPADDRESS_ROW based on the IP_ADAPTER_UNICAST_ADDRESS and IP_ADAPTER_ADDRESSES data structures which we already have. */ > Alternatively, can't we start with GetUnicastIpAddressTable() as my > original code did, and if we want to check whether an interface is down > before we steal the address from it, use GetIfEntry2() to find out? > > Using GetIfEntry2() is probably a saner way to find the InterfaceIndex > for the Wintun itself, which I was dredging the registry for manually. > > > I'd like to be consistent about clearing the 'conflicting' addresses > and setting the address on the Wintun interface. Whatever we do in > OpenConnect for Legacy IP we should also do for IPv6. It looks like > you're clearing the conflicting addresses for both families, but we > still aren't *setting* the IPv6 address from the C code? Let's save the OpenConnect-specific decisions, but… It seems to me that we've identifying a couple of tasks that many users of Wintun would need, and which are (in my opinion) quite tedious to implement robustly in Windows: 1. Identifying the “interface index” of the newly-created adapter (for use with 'netsh', etc.). 2. Reclaiming desired Layer3 (IP) addresses from other non-UP adapters to which they may already be assigned. If the Wintun developers are amenable to it, these both seem like they could be useful additions to Wintun itself. Dan ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: Duplicate IP address, and permissions problems on Windows 2021-04-08 17:53 ` Daniel Lenski @ 2021-04-10 9:25 ` David Woodhouse 0 siblings, 0 replies; 11+ messages in thread From: David Woodhouse @ 2021-04-10 9:25 UTC (permalink / raw) To: Daniel Lenski; +Cc: Jason A. Donenfeld, WireGuard mailing list [-- Attachment #1: Type: text/plain, Size: 2964 bytes --] On Thu, 2021-04-08 at 10:53 -0700, Daniel Lenski wrote: > On Thu, Apr 8, 2021 at 9:59 AM David Woodhouse <dwmw2@infradead.org> wrote: > > Hm, your description doesn't match the code I see at that link. > > > > You're using GetAdaptersAddresses() which gives you the UP/DOWN status > > as well as the addresses, and you iterate over those. The loop is > > > > ∀ adapter, ∀ Unicast address on that adapter: > > Check if it's our Legacy IP or IPv6 address. > > > > That isn't O(n²), is it? It's still O(n) of the total number of unicast > > addresses in the system? > > It's O(n²) the number of unicast addresses, because there's an extra layer… > > ∀ adapter, ∀ Unicast address on that adapter (iterating via > GetAdaptersAddresses) > 1. Check if it's using our Legacy IP or IPv6 address. > 2. If yes, then check if the other adapter is UP or non-UP > 3. If non-UP, then… > ∀ Unicast address on the system (iterating via > GetUnicastIpAddressTable(), since the other one maddeningly lacks an > API to delete addresses) > 2. If non-UP, then steal/delete/reclaim the desired address from it. Nah, that doesn't make it O(n²) because anything after step 1 doesn't actually happen for *every* address. It only happens for the one or two addresses which match our Legacy IP or IPv6 address. And we *know* there can be only one match for each because that's the whole point of this exercise, right? So it's O(n) for checking all the addresses in the system against our own address(es), then O(n) for each of up to two addresses that get matches. O(3n) is still O(n). And I still think you can fix it just to be O(1n) this way anyway, can't you: > > Alternatively, can't we start with GetUnicastIpAddressTable() as my > > original code did, and if we want to check whether an interface is down > > before we steal the address from it, use GetIfEntry2() to find out? > > > > Using GetIfEntry2() is probably a saner way to find the InterfaceIndex > > for the Wintun itself, which I was dredging the registry for manually. > Let's save the OpenConnect-specific decisions, but… > > It seems to me that we've identifying a couple of tasks that many > users of Wintun would need, and which are (in my opinion) quite > tedious to implement robustly in Windows: > > 1. Identifying the “interface index” of the newly-created adapter (for > use with 'netsh', etc.). > 2. Reclaiming desired Layer3 (IP) addresses from other non-UP adapters > to which they may already be assigned. > > If the Wintun developers are amenable to it, these both seem like they > could be useful additions to Wintun itself. Along with the driver extension to add headroom/tailroom to TUN_PACKET, I agree. I'd be interested to hear Jason's and Simon's thoughts about those in principle, before we rephrase those suggestions in the form of a pull request. [-- Attachment #2: smime.p7s --] [-- Type: application/x-pkcs7-signature, Size: 5174 bytes --] ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2021-04-12 17:52 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2021-04-06 11:29 Duplicate IP address, and permissions problems on Windows David Woodhouse 2021-04-07 0:17 ` Jason A. Donenfeld 2021-04-07 8:18 ` David Woodhouse 2021-04-07 23:05 ` Daniel Lenski 2021-04-12 17:50 ` Jason A. Donenfeld 2021-04-07 23:00 ` Daniel Lenski 2021-04-08 8:46 ` David Woodhouse 2021-04-08 16:09 ` Daniel Lenski 2021-04-08 16:59 ` David Woodhouse 2021-04-08 17:53 ` Daniel Lenski 2021-04-10 9:25 ` David Woodhouse
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).