Which addresses should be published via global discovery?

This topic has been brought up several times in various Github issues over the last few days:

At the time of writing, there are 4 different ways to determine publishable addresses:

  • external IP via STUN
  • external IP witnessed by the global discovery server
  • explicitly bound address
  • IPv4 LAN addresses via interface lookup

We should discuss which address we want to publish via the global discovery mechanism and to what extent the user should be able to change this.

1 Like

The app might also be a reason to allow externally provided addresses:

1 Like

@calmh @AudriusButkevicius @acolomb any opinions?

1 Like

I agree with your summary in #8453.

First, a general remark: When configuring routers to open ports, would it be reasonable to expect that a whole prefix could be opened for a certain device, which encompasses all its temporary (privacy) addresses as well?

In my experience, many cheapo / provider-supplied routers don’t even allow opening the IPv6 firewall, while most do have some kind of interface for IPv4 port forwarding. That means users who configure stable EUI-64 addresses probably know what they’re doing, take care that they choose a router which supports it, but are then out of luck because the discovery server does not get that address. More average users will be out of luck anyway unless we start doing some kind of automatic firewall punching or port mapping for IPv6.

Are these actually different? The only chance I see is when you’re not actually using a global discovery server, but your own with an on-premise, non-global address. It might be contacted through an fe80::* address for example, while STUN would yield the external, global address.

These should definitely be published if qualified, e.g. not link-local.

Do we have any way to dial link-local addresses received from the (global) discovery server? It’s not really defined where they should be used, but we could just try on every local interface in turn?

This should definitely be published IMO. Same question as above applies, whether link-local or site-local addresses should be filtered out. But other devices local to the same site might need them if local multicast is not available (such as in corporate Wi-Fi setups).

I agree with your assessment @bt90 that these are not really a solution to the firewall problem, as the prefix may change and the listen address would need to be adjusted frequently.

I would welcome such a mechanism for IPv6 as well. These are pretty useless for tracking purposes, so not relevant to privacy. I don’t think we’d need an opt-out configuration for these.

We already have a mechanism to opt-out from global discovery at all and from STUN separately (haven’t tested if stunServer="" works or falls back to default). Explicitly configured listenAddresses are put there for a reason, so I guess announcing them is legitimate. Any other addresses should be made opt-in.

That would extend the list to include globally routable addresses taken from local interfaces, temporary or EUI-64. There may be legitimate reasons to announce them, but doing so by default is a little risky. How would a user handle this? Getting no direct IPv6 connection, they would first enable the boolean option, then hope that hole-punching works, otherwise go on to open the router’s firewall. At this point, it would not require to explicitly set the listen address, which is even less likely to keep working than a router being able to automatically adjust the prefix in its firewall rules. If hole-punching had not worked thus far, it’s unlikely to start working for any of the extra addresses, but announcing them will help make any manual router configuration take effect.

Regarding privacy, it’s not quite easy to query a random user’s IP addresses because the device ID must be known in advance. But a formerly connected device turned malicious or a forum post containing the ID would suffice to exploit the discovery server oracle, hence the opt-in suggestion.

As for spoofing, I think we cannot rule it out completely. If one has access to the REST API on the device, they can probably just access the Syncthing device certificate and key and use it to feed the discosrv with spoofed addresses. Regardless of whether the official Syncthing binary provides an official way to feed addresses. So it gets a little easier, but was already possible. And I think having wrong addresses for a remote device on the discosrv is not very problematic. To use it for a DDoS attack, one would still need a lot of devices which have the victim configured as a remote to be dialed–there are much more efficient ways to achieve that.

If the only way to enumerate interface addresses on e.g. Android is to have the wrapper feed them to the backend, then I wouldn’t oppose doing that via REST. If there are ways around it, we should definitely avoid it though.

Take all my comments with a grain of salt, as I don’t feel like I have a complete picture of all the involved networking mechanics and privacy consequences. Just my perspective as a power user :wink:

1 Like

I don’t think so. But the global discovery server acts as poor man’s STUN this way.

Well, it might cause a few failed dialing attempts which is probably already the case or am I missing something else?

Syncthing is also FOSS which includes freedom as in “freedom to shoot yourself in the foot”. Otherwise i agree with your risk assessment. Once trust is established with a device, we should assume that we can safely dial the addresses it publishes.

Either that or somehow create bindings as @imsodin pointed out:

But I think the REST API approach is much cleaner, as it doesn’t pollute the Syncthing binary with Android API shenanigans or complicates the build.

1 Like

I’m opposed to announcing globally unique addresses by default. I think it can be generally expected that one’s external IP address is visible - it’s impossible to communicate otherwise. I don’t think it’s expected that internal network topology is published to the outside world.

STUN vs seen-by-discovery are separate in our case because one is UDP and the other TCP.

1 Like

Not by default, but you could live with an opt-in announceGlobalInterfaceAddresses boolean? It would affect all temporary and static addresses taken from the interfaces.

1 Like

Sure, I see no philosophical reason to oppose an opt-in to publish whatever.

2 Likes

That’s already the case if we publish any 2001 prefixed IPv6 address.

This is perfectly fine for IPv4, but does not translate well to IPv6 as there is no single external address. You usually have multiple globally unique addresses per device. Temporary IPv6 addresses are the norm and can be considered as throwaway addresses just for outgoing connections. STUN and seen-by-discovery would both only report a temporary address, which cannot be configured in router firewalls and therefore cannot be used later if we would add UPnP pinhole support.

Apart from ULA and link-local addresses, there is no difference between internal and external addresses in IPv6. Publishing the non-temporary IPv6 of a device isn’t a privacy nightmare either if the user doesn’t deliberately switch to EUI-64 address generation. The OS will generate a new randomized address once in a while.

1 Like

We don’t, except for

the “external address”, which for our purposes is the one seen by the discovery server. (Edit: or, if you specifically bind to an address, then we publish that.)

That’s an assumption. NAT is a thing in the IPv6 world as well, in various variants, and the prefix on the end host may not be the prefix visible to the rest of the world for various reasons.

1 Like

Agreed.

I think @acolomb proposal of a dedicated option would be the best.

Maybe we can also add one for user specified addresses. This might solve the Android problem without having to provide a REST endpoint. Users with more unusual network configurations can also take advantage of this.

2 Likes

Providing a configuration option to enter arbitrary addresses sounds like a large footgun to me. Although the consequence of other devices dialing a wrong address is rather harmless. But I see this info as not very static, otherwise one could already go the other way and configure the addresses on the remote devices statically. Therefore I prefer the REST solution, which is a little harder to handle, but forces the user to have some kind of daemon or script feeding an up-to-date address at least once after every Syncthing restart. No chance to leave a forgotten config option with outdated values behind.

1 Like

I keep thinking about hole punching with temporary addresses being active. Is it really granted that both sides end up with the same origin/destination IP? e.g the host uses temporary address A for publishing but address B to connect to the other device.

Does golang support RFC 5014? With that we could pass something like IPV6_PREFER_SRC_PUBLIC to the socket and force Syncthing to use the non-temporary IP address for outgoing traffic. That would cause thr discovery server to pickup the correct IP and also increase the chance for holepunching to succeed.

1 Like