syncthing icon indicating copy to clipboard operation
syncthing copied to clipboard

Binding to specific interfaces/IPs for outgoing connections

Open nikitakuklev opened this issue 8 years ago • 4 comments

This issue has been touched upon a couple times, but I thought I should make it a bit clearer with my use case, and ask for comments on feasibility.

Use case: Win10 system with fast (but metered) wired connection (LAN) and slower but essentially unlimited wifi (WF), both behind NAT. There is no port forwarding/UPnP, but remote nodes do have it so direct connections work on both. Interface and gateway (route) metrics are such that everything goes to LAN by default but I want ST to use WF exclusively to save on traffic.

Problem 1: setting listen IP to $WF_IP$ does work but only for listen connections...global discovery/outgoing connections still go out LAN (as expected due to OS routing) and so does all the traffic that follows. (there is also a problem with discovery server not auto-adding source IP anymore, but that is intended as per v3 protocol specs)

Problem 2: even if setting outgoing IP was possible, $WF_IP$ changes frequently - hardcoding it is useless

Desired feature: ability to pick interface and (optionally) specific IP to bind to for all internet traffic (global discovery/relay/direct connections). For example, something like in qBitTorrent: alt text alt text

Note that IP binding is useful since single interface can have multiple IPs due to aliases (in my case, main IP is 10.1.1.10 and there is IP alias 10.1.1.11 with 'SkipAsSource=true', such that applications that specifically bind to it are routed over VPN but other traffic only uses 10.1.1.10).

In case both options are not selected, ST should defer to default OS routing as it does now. If only interface is specified, it should follow system routing behavior for that interface (if not known/hard, just pick single IP like libtorrent does). Option to choose fallback strategy in case of interface/IP being unavailable is necessary.

Besides my use case, there are other ways this can be useful. For example, to prevent flip-flopping over intermittent connections (wi-fi or cellular modem/tether), to limit sharing to home IP, or to route ST traffic over VPN interface.

nikitakuklev avatar Feb 15 '17 02:02 nikitakuklev

Feasibility: (disclaimer - I don't know much Go) It seems that one can obtain a list of interface names and then extract IPs, one of which is then supplied to various Dialers via laddr (such as in here). Since net.Dial used in lib/dialer doesn't have that argument, would need to convert to TCPConn.DialTCP, taking care to separate local UDP stuff.

Concerns I could identify:

  • IP address binding != interface binding (due to routing), and libtorrent for instance solves this via SO_BINDTODEVICE socket if available, with IP binding as fallback. Breaking this down by OS: -- Windows has no interface binding but it uses strong ES model, so specifying source IP address is equivalent to specifying interface. This also applies to most *nix OSes. -- Linux uses weak ES by default, but can bind interfaces. Doing it with Dialer is hacky and syscall sockets should be used instead. This however would require a major network rewrite unless result can be used with existing Dialer (can PacketConn be swapped in somehow)? In the end, it may be simpler to assume a sane routing config and just bind IP on all platforms.
  • Settings need to apply to internet connections but not affect local discovery and traffic - i.e. if LAN is local and WF is internet and only output interface, have to either somehow defer to system routing to reach local peers or keep track of who is on what interface. Also related to #1305, #1312. Libtorrent solves this by specifically testing each IP on whether it is local (by checking against mask) before connecting.
  • Need to play nice with proxies (replace underlying proxy Dialer, proxy.Direct, which uses net.Dial?)
  • What happens if connection comes in on interface we listen to, but that is not in outgoing or local lists? Should probably force to listen on all outgoing+local interfaces and ignore anything else.
  • Need to monitor interface and IP status changes (discussed a bit in #2941). This can happen with DHCP renewals or usb wi-fi dongles or on flaky connections. Depending on settings, need to either rescan available IPs and rebind as fallback, revert to OS routing, or wait until interface/IP returns (like current proxy no fallback flag).
  • Dualstack IPv4/IPv6 binding and tracking seems complicated, especially if number and availability of IPs is not homogeneous...scream silently in logs
  • GUI implementation of dynamic multi-selection boxes is not available (could probably get away with string boxes in advanced settings though). Need to refresh options on any network change, or specifically note that restart is required like in above screenshots.

I realize this is hard to implement properly but it does provide an opportunity to resolve at least several open feature requests, especially if local interface selection is also added in. Before valiantly trying and failing to code something, I thought I should ask if anyone has tried it or can see major problems with above approach.


P.S. For those that have this issue and want a temporary fix - you can use a local SOCKS5 proxy that can bind IPs, such as 3proxy. Combined with the no fallback option and a small PS cmdlet to find current IP+set env vars, this effectively imitates interface binding. Works with SyncTrayzor too.

nikitakuklev avatar Feb 15 '17 02:02 nikitakuklev

Thanks for a great writeup and feasibility analysis. For what it's worth it sounds to me like "just" binding outgoing connections to a source address would solve most of this issue. Doing so is simple enough, and also gives you something that you could act on to accomplish the rest - that is, in Linux, you could for example filter outgoing traffic based on source network in iptables and override the routing decision there.

An extension to that would be to bind to an interface UI wise, but still bind to a source address at connection time, by just listing the addresses on the specific interface at connection time.

Neither of those things would be super difficult or require very much in terms of re-engineering the existing stuff, so that's probably where I would start.

calmh avatar Feb 15 '17 06:02 calmh

What's the status of this issue? Are we just waiting for someone to write a patch?

xordspar0 avatar Sep 04 '18 03:09 xordspar0

Yes

calmh avatar Sep 04 '18 04:09 calmh