Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC, nomerge] PCP IPv4 portmap+IPv6 pinhole test #30005

Closed
wants to merge 3 commits into from

Conversation

laanwj
Copy link
Member

@laanwj laanwj commented Apr 30, 2024

The overarching goal here is to increase the number of connectable nodes that are not in the big datacenters.

Context: IPv6 doesn't have NAT. Computers behind a router tend to get a globally routable address. However, by default this address is usually completely firewalled for incoming connections, as a security measure. See issue #17012.

This is where "pinholing" comes in. By sending a request, a machine on the network can request a port to be opened. This is similar to requesting a port forward for IPv4 but simpler.

This PR implements the so-called PCP (Port Control Protocol) from RFC6887. This is a simple UDP based protocol with fixed-size packets, so is safe to possibly even enable by default. Much simpler than UPnP which also has commands to open a pinhole, but relies on SSDP, HTTP, and XML parsing (and miniupnpc has had serious RCEs in the past). This implementation is self-contained, no external dependency is added.

Before integrating it into Bitcoin Core i would first like to investigate whether this implementation is correct and whether routers support it. So this adds a binary, ipv6-pinhole-test, which:

  • Enumerates local publicly routable IPv6 addresses
  • Gets the default gateway to get the PCP endpoint
  • Requests pinholes for 100 seconds to port 1234 on all addreses, and prints the result.

i've tested this on two routers myself--Turris Omnia and Fritz!Box, where it worked. Please help testing, by just running it behind the router of your choice!

For now, this is for Linux only. Implementing it for other platforms requires implementing a way to get the default route. i'll get to this later.

Be careful with publicly posting the full output of this program-it will contain your IP address information.

[skip ci]

@DrahtBot
Copy link
Contributor

DrahtBot commented Apr 30, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept NACK fanquake
Concept ACK Sjors

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #30040 (util, refactor: Switch to value-initialization by hebasto)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@Sjors
Copy link
Member

Sjors commented Apr 30, 2024

Tried with a router running OPNsense:

2024-04-30T12:16:13Z [net:debug] pcp6: gateway: ...
2024-04-30T12:16:13Z [net:warning] pcp6: Opening pinhole for addr: ...
2024-04-30T12:16:14Z [net:debug] pcp6: Timeout
2024-04-30T12:16:14Z [net:debug] pcp6: Retrying (1)
2024-04-30T12:16:15Z [net:debug] pcp6: Timeout
2024-04-30T12:16:15Z [net:debug] pcp6: Retrying (2)
2024-04-30T12:16:16Z [net:debug] pcp6: Timeout
2024-04-30T12:16:16Z [net:debug] pcp6: Giving up after 3 tries
2024-04-30T12:16:16Z [net:warning] pcp6: Opening pinhole for addr: ...
2024-04-30T12:16:17Z [net:debug] pcp6: Timeout
2024-04-30T12:16:17Z [net:debug] pcp6: Retrying (1)
2024-04-30T12:16:18Z [net:debug] pcp6: Timeout
2024-04-30T12:16:18Z [net:debug] pcp6: Retrying (2)
2024-04-30T12:16:19Z [net:debug] pcp6: Timeout
2024-04-30T12:16:19Z [net:debug] pcp6: Giving up after 3 tries
2024-04-30T12:16:19Z [net:warning] pcp6: Opening pinhole for addr: ...
2024-04-30T12:16:20Z [net:debug] pcp6: Timeout
2024-04-30T12:16:20Z [net:debug] pcp6: Retrying (1)
2024-04-30T12:16:21Z [net:debug] pcp6: Timeout
2024-04-30T12:16:21Z [net:debug] pcp6: Retrying (2)
2024-04-30T12:16:22Z [net:debug] pcp6: Timeout
2024-04-30T12:16:22Z [net:debug] pcp6: Giving up after 3 tries

I probably have to actively turn on some permission.

@laanwj
Copy link
Member Author

laanwj commented Apr 30, 2024

It may be some router setting to enable it, the more secure routers have these kind of protocols disabled by default. Or maybe it doesn't support it. There isn't any reply, not even 2 NOT_AUTHORIZED (but nice to see the retry code is working 😄 ).

@Sjors
Copy link
Member

Sjors commented Apr 30, 2024

Unfortunately I'm not able to find any documentation for this.

There is a UPnP plugin, but that's not what you're using (and probably best left uninstalled).

Anyway, people who run OPNsense will now how to manually forward a port, so that's not really the target demographic here.

@laanwj
Copy link
Member Author

laanwj commented Apr 30, 2024

There is a UPnP plugin, but that's not what you're using (and probably best left uninstalled).

Yea, according to this list, UPnP and PCP is the same plugin. It could be that they can be turned on and off seperately in the plugin configuration (it's similar for OpenWRT). The most well-known open source implementation of a PCP server is... ironically, miniupnpd.

@Sjors
Copy link
Member

Sjors commented May 2, 2024

Ah, that's confusing. The OPNsense is indeed just MiniUPnPd and described as "Universal Plug and Play (UPnP IGD & PCP/NAT-PMP) Service", in other words: all the things :-)

It lets you choose what to enable, in this case I selected "PCP/NAT-PMP Port Mapping".

Now your utility is happy.

On Ubuntu I additionally had to do sudo ufw allow 1234 though for the nc connection to succeed (ufw is disabled by default on new systems).

I tested that the machine was indeed reachable from the outside world (and that it no longer is after 120 seconds, existing connections are preserved).

Copy link
Member

@Sjors Sjors left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK. RFC 6887 is quite readable and simple for a client to support.

IPv6 always returns multiple addresses. I'm not sure what the logic behind that is, but should we gossiping and pinholing all of them? Or is one "supposed" to be used?

Why not also use PCP for IPv4 NAT? If it's widely supported enough, maybe it lets us drop both upnp and natpmp from the dependencies (immediately or at some point in the future if there's actually a problem with them).

This appears to be trivial:

When the address field holds an IPv4 address, an IPv4-mapped IPv6 address RFC4291 is used (::ffff:0:0/96).

One nice feature of PCP is that it gives us the external IP(v4) address. We could use that in lieu of / in addition to asking our peers for it. But note that section 11.6 recommends against doing this without also requesting a port forward / pinhole.

src/Makefile.am Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
src/ipv6-pinhole-test.cpp Outdated Show resolved Hide resolved
@laanwj
Copy link
Member Author

laanwj commented May 2, 2024

Thanks for the review!

IPv6 always returns multiple addresses. I'm not sure what the logic behind that is, but should we gossiping and pinholing all of them? Or is one "supposed" to be used?

That's a good question. Sometimes routers will give out temporary addresses as well as permanent addresses, i'm not sure it's possible to discover the intent of an address at application level. If so it is going to be OS specific. Currently in master the application Discover()s alll publicly routable IPv6 addresses, which seems like a sensible default.

More advanced networking users will hand-configure things anyway. And if specific addresses are being -listened on, we should heed that.

Why not also use PCP for IPv4 NAT? If it's widely supported enough, maybe it lets us drop both upnp and natpmp from the dependencies (immediately or at some point in the future if there's actually a problem with them).

i agree with this, but it's not as urgent. We have good coverage for IPv4 port mapping methods, i'm kind of hestitant to touch that, given how hard it already is to find people to test these kind of things 😅

So called "dual stack lite" are getting quite popular for ISPs, which essentially means no publicly routable IPv4 ("carrier-grade NAT") only IPv6. So for home users this is a better focus.

One nice feature of PCP is that it gives us the external IP(v4) address.

AFAIK, UPnP and natpmp also give you this information. In regard to IPv4, natpmp and pcp are essentially the same with slightly different packet layout.

@fanquake
Copy link
Member

fanquake commented May 2, 2024

Could you rebase this on master, now that we are using the latest version of miniupnpc (#29707).

@Sjors
Copy link
Member

Sjors commented May 2, 2024

i'm kind of hestitant to touch that, given how hard it already is to find people to test these kind of things

For now we could add support in the test, but stick to IPv6 in the implementation. OTOH both UPnP and NAP-PMP are off by default. Adding another off-by-default checkbox "IPv6 pinhole" to the GUI seems pretty confusing, and probably doesn't increase the chance of users trying this.

@laanwj
Copy link
Member Author

laanwj commented May 2, 2024

Could you rebase this on master, now that we are using the latest version of miniupnpc (#29707).

Yes, but first want to address @Sjors's comments. Mind that this adds an utility and is orthogonal to UPnP. Adding IPv6 pinholing through UPnP would be useful too (for routers that support that, and not PCP), so we'll likely want to be able to do both in the eventual design like we do for natpmp/UPnP for IPv4.

For now we could add support in the test, but stick to IPv6 in the implementation.

Sure, could. But mind that IPv4 will be mostly a different path. Default gateway discovery is different (mind that i still have to implement that for Windows and MacOS), port mapping semantics are slightly different from pinholing, the code will have to handle IPv4 addresses. This added complexity makes it harder to review, too.

Adding another off-by-default checkbox "IPv6 pinhole" to the GUI seems pretty confusing, and probably doesn't increase the chance of users trying this.

i would like it enabled by default, i am not sure people have that much confidence in my code lol. But it's a discussion for the integration PR, we'll probably aim to merge it with disabled-default first to move forward at all.

@fanquake
Copy link
Member

fanquake commented May 2, 2024

Mind that this adds an utility and is orthogonal to UPnP.

~0. Right, I have misunderstood what this is/does. This could be a useful tool (for some % of users of this software), but I don't think this repository is the right place for it to live, or that it fits into the set of things we should be maintaining here (I'm really suprised there isn't software that already exists, for doing the same thing, that we could just point users to?).

@laanwj
Copy link
Member Author

laanwj commented May 2, 2024

This is a temporary PR for people to test this code, i intend to integrate it in bitcoind just like the current IPv4 port mapping stuff. I did not intend it to be a useful tool to keep around. This is why the title says "nomerge".

@fanquake
Copy link
Member

fanquake commented May 2, 2024

I did not intend it to be a useful tool to keep around.

Fair enough, I'll -redirect my NACK to this thread: #30005 (comment).

@Sjors
Copy link
Member

Sjors commented May 2, 2024

Sure, could. But mind that IPv4 will be mostly a different path.

I see. I guess future UI confusion can ben prevented by calling this "PCP" with a note saying that it works with IPv6 only for now.

The GUI could also hide the kitchen sink of methods under some advanced toggle. In any case that doesn't affect testing.

@laanwj
Copy link
Member Author

laanwj commented May 2, 2024

I see. I guess future UI confusion can ben prevented by calling this "PCP" with a note saying that it works with IPv6 only for now.

OK, i see your point now. Let's replace NAT-PMP for IPv4 at the same time, so we keep the same number of options (PCP and UPnP), and lose one dependency (libnatpnp).

@Sjors
Copy link
Member

Sjors commented May 2, 2024

I'm really suprised there isn't software that already exists, for doing the same thing, that we could just point users to?

If there's a library that does the same thing, and doesn't have 1 million lines of code, then it seems fine to include it. We can also make it a library under the core repo, which perhaps other projects will find useful for its simplicity. At least now I have a sense of what that library should be doing.

I don't think we should ask users to install a separate piece of software. For HWI I understand the argument of not wanting to include USB support in our codebase. But this protocol is much simpler (one UDP message and one reply).

Some extra maintenance work seems justified here because there's at least a plausible risk that we're running short on listening nodes, if we turn on Asmap: #16599 (comment)

Also, I was under the assumption that the reason we added NAT-PMP originally was to eventually replace UPnP (see #11902). Have those concerns been reduced? Do you see PCP as a potential replacement for both NAT-PMP and UPnP? Or not sure, let's keep them all around for now?

@laanwj
Copy link
Member Author

laanwj commented May 2, 2024

i mean, there's probably some crappy C implementation of PCP out there that we could include, but this one is written in C++, it integrates with bitcoin's networking and logging, well commented, and it's straightforward enough. Besides, any code included from a third party would have to be reviewed just as well.

i've already agreed to add IPv4 support so it can replace libnatpmp.

@fanquake's comment was based on a misunderstanding.

@DrahtBot DrahtBot removed the CI failed label May 3, 2024
laanwj added 3 commits May 4, 2024 17:36
Still default to 10, of course. Need this for parsing a hexadecimal integer.
@laanwj laanwj force-pushed the 2024-04-pcp-pinhole-test branch from f66e2e8 to e3d4765 Compare May 4, 2024 15:46
@laanwj laanwj changed the title [PoC, nomerge] IPv6 PCP pinhole test [PoC, nomerge] PCP IPv4 portmap+IPv6 pinhole test May 4, 2024
@laanwj
Copy link
Member Author

laanwj commented May 4, 2024

@Sjors It creates a IPv4 as well as IPv6 mappings now. The added complexity isn't even too bad, mostly a matter of using CNetAddr/CService/Sock abstraction that we already have.

@laanwj
Copy link
Member Author

laanwj commented May 5, 2024

Continued in #30043.

@laanwj laanwj closed this May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants