-
-
Notifications
You must be signed in to change notification settings - Fork 339
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
Allow setting source interface in open_tcp_stream #275
Comments
Question: how does #39 has some discussion of SO_REUSEPORT details, for reference. |
Here's a possible API that might be a bit simpler to use: allow specifying a list of source addresses, and each socket walks down the list and uses the first one that it can. So Do people need the ability to control the outgoing port? We could also allow |
To determine if an IP address can be used with an entry returned from If so, more than two entries in that list doesn't really make sense, correct? |
@miracle2k I guess I was imagining that we might write a loop like: for source in preferred_sources:
try:
await sock.bind(source)
except OSError:
# Try the next source
continue
else:
# The bind worked, we can use this socket
break
else:
# no sources worked, give up on this socket It's extremely not-smart, which is in some ways a benefit, because it arguably makes it smarter :-). For example, it could handle the case where someone lists multiple fully-specified I haven't through it through enough to come to any firm conclusion on which way is best, but I do like the idea of not having to deal with parsing IP addresses. (Though the |
Somewhat related consideration: setting UDP packet source address. I'm running a DNS server on localhost, and following the convention of systemd-resolved, lookups are done on 127.0.0.53 rather than 127.0.0.1. Apparently there is no easy way to answer these packets with the correct source address with Python sockets (even at low level), and packets are always sent with source 127.0.0.1 (interface's main address) and then rejected by recipient because of wrong source address. |
The httpcore project is asking us for this feature – see encode/httpcore#88, encode/httpcore#100, #1642 I poked at it a bit more recently. This is a frustrating API to design, because the use cases are not at all clear. For example, the httpcore PR allows setting a source port – is that important? I don't know. It definitely creates awkwardness for happy eyeballs though, since if you naively try to make multiple sockets that are all bound to the same source port then it's not going to work. (Maybe some kind of Things I do know:
So overall, my impression is that this is mostly useful to enable slightly-janky-but-understandable hacks and workarounds for weird routing configurations. Fair enough. Implementing it is pretty straightforward. I think the questions to answer are:
It's not actually that hard to implement even the most complicated all-the-bells-and-whistles version... but testing it is super annoying, because there are so many different tricky combinations of cases. And to make testing even more fun, on most test machines you can't even assume that there are multiple addresses available to attempt binding to. So, given all this, my inclination is to start by implementing the simplest possible version: let the user pass in a single source host, no port, no fallback sequence, no clever optimizations. That's sufficient to handle all the actual known use cases, can be extended later if necessary, and seems like an appropriate amount of effort to put into a feature that's only used for rare workarounds. One wrinkle is that in their draft PR, the httpx folks do want to support setting a source port: encode/httpcore#100. Maybe we can convince them to skip that feature? It doesn't look great for Trio if downstream projects have to put stuff in their docs like "NOTE: the XX feature is supported on all backends except Trio.", regardless of whether the XX feature is actually useful or not. |
Oh, here's some more discussion of this on the httpx tracker, with more use cases: encode/httpx#755 AFAICT they're all similar to what I said above, and in particular I don't see any requests to control the source port. |
Setting the source port makes sense on UDP, but with TCP I don't see the usecase. Worse, today you set your fancy source port (because you can, y'know), next month your co-worker tries to run your client on two destinations simultaneously, whcih happen to resolve to the same IP address, which suddenly won't work because the address+port combo is no longer unique. Owch. Linux can happily assign a specific IP address to multiple interfaces. Worse, binding to a local address doesn't actually force packets to use the interface / any of the interfaces which the address is bound to. So setting that local address is not sufficient if you want to control which interface to use. There's a SO_BINDTODEVICE socket option to do that. Supporting it thus makes sense and is pretty easy to implement (if not exactly easy to test). |
Unfortunately |
Probably not too important, but the restriction of SO_BINDTODEVICE to root was removed in the 5.7 version of the Linux kernel. Who knows when distributions will pick this change up, though. |
@bwelling For what it's worth, we're already testing 5.6 with Fedora 32 in CI, and will be able to test a newer version when Fedora 33 comes out in a few months. |
Right now
open_tcp_stream
has no option to control which outgoing interface the connections are made from. We should make this possible.Subtleties:
On Linux 4.2+, there's
sock.setsockopt(IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, 1); sock.bind((address, 0))
which means "use this host, but delay picking the port until I callconnect
". We should use this if available. We should not useSO_REUSEADDR
, because then it's possible to bind to a port that will fail when we actually callconnect
(because there might be a TIME_WAIT with the same 4-tuple).So maybe something like:
source_host={AF_INET: "...", AF_INET6: "..."}
?Should we also allow
source_host="1.2.3.4"
and auto-expand it into{AF_INET: "1.2.3.4"}
?If one is not specified, what happens? Maybe we should treat
INADDR_ANY
as meaning "you can use this protocol, and I don't care what host you originate from", and missing as meaning "don't use this protocol"? That could be nice since it would also give a way to say "I just want you to use IPv6", albeit not an obvious one.The text was updated successfully, but these errors were encountered: