-
Notifications
You must be signed in to change notification settings - Fork 451
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
Avoid race conditions when starting Socks5 servers and REST API by allowing OS to choose a free port #7565
Conversation
settings["listen_interfaces"] = "0.0.0.0:%d" % libtorrent_port | ||
|
||
# We do not specify the port in settings dict as we allow libtorrent to automatically choose the port | ||
# settings["listen_interfaces"] = "0.0.0.0:%d" % libtorrent_port |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allowing users to specify a port for torrent traffic is critical functionality for some users. The other ports probably don't matter as much but I'd leave the libtorrent port config option as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for this link!
Thinking about this, the current Tribler logic regarding the libtorrent port number handling does not look correct to me. This is how, in my understanding, the current logic of DownloadManager.create_session()
works:
- Initially, in LibtorrentSettings, the port value is
None
by default (and a user can set a specific value):
class LibtorrentSettings(TriblerConfigSection):
...
port: Optional[int] = None
...
- The value is read from config, and an empty value is replaced with a random free port:
libtorrent_port = self.config.port or default_network_utils.get_random_free_port()
- The resulting port value is passed to libtorrent:
settings["listen_interfaces"] = "0.0.0.0:%d" % libtorrent_port
...
self.set_session_settings(ltsession, settings)
- The libtorrent is requested to listen on a port in a range, starting from a specified port value:
ltsession.listen_on(libtorrent_port, libtorrent_port + 10)
- Then, if the actual port used by libtorrent is different from the port that was initially specified, the actual port value is stored in the config:
if libtorrent_port != ltsession.listen_port() and store_listen_port:
self.config.port = ltsession.listen_port()
(the store_listen_port
parameter value is True by default and is not specified when the DownloadManager.create_session
method is called).
From this, my conclusion is the following: even when a user specifies the libtorrent port value in settings, it is possible that the value will be changed to a different value (at step 3) if the original port is already occupied. Then the value provided by a user will be overridden in settings by a new value without any confirmation by a user.
From what I currently understand, the correct logic should work in the following way:
- If the port value is explicitly specified by a user, it should be used. If the specified port is already occupied, Tribler should not use a different port silently. Instead, it should notify the user that the specified port is unavailable and stop.
- If the value is not specified, Tribler should allow libtorrent to choose the port automatically. In that case, Tribler should not store the randomly assigned port value in
LibtorrentSettings
, as it is not guaranteed that the same port will be available next time, and it is not important to use the same port next time. Only the port value explicitly specified by a user should go to LibtorrentSettings.
@qstokkink what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good 👍 That's what I would expect from my torrent client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the offline discussion with @xoriole we decided to extract libtorrent port handling changes to a separate PR
79a0efc
to
fcf7877
Compare
3d15b82
to
fac71ee
Compare
fac71ee
to
f8b6be2
Compare
f8b6be2
to
f83bc75
Compare
f83bc75
to
e5b014e
Compare
This PR fixes #7564. It stops using
default_network_utils.get_random_free_port()
and passes port value zero when starting a server to allow OS to choose a free port without race conditions.The PR consists of several commits:
get_random_free_port()
when starting a new Socks5Serverevents_manager.connect(...)
toevents_manager.connect_to_core(...)
. The reason for this is theconnect
name is overused: the same name is used for subscribing to PyQt signals, for openingUdpTrackerSession
, and for connecting to the sqlite3 database in ProcessManager, so it is better to have a distinct name to find all places when the specific method is used easily.get_random_free_port()
and detect the free port for Tribler Core's REST API usage. Instead, Tribler Core requests a free port directly from OS when starting the server and then passes the port value to Tribler GUI. Now it is unnecessary to have a loop over a range of ports on the Coree side and attempt to start a server with a possible retry, as the suitable port is available from the first attempt.As a result:
Previously, the documentation said 20100 is a default port value for Swagger UI and REST API. It is not entirely true, as the port number was taken from a range. This part of the documentation is clarified: the user should specify the port value in the
CORE_API_PORT
environment variable to start the REST API and Swagger UI on a specific port.