public ip address is not detected from loopback interface
on some machines, the public ip address
is assigned to the loopback interface lo
for "fully redundant networking" (high availability network setup)
for example at feralhosting.com
but then, qbittorrent fails to connect to trackers and DHT peers, and all torrents are "stalled" aka "no connections"
the qbittorrent status bar says
DHT: 0 nodes, green globe, green gauge, down 0 B/s (0 B), up 0 B/s (140 KiB)
other libs
this just works with other bittorrent libraries
- rtorrent (todo verify)
- rqbit - written in rust, based on the tokio async runtime
- anacrolix/torrent - written in go
example
my lo device looks like this
$ ip a s lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever
inet 10.0.0.1/32 scope global lo valid_lft forever preferred_lft forever
inet 185.21.216.155/32 scope global lo
my public ip address is
$ curl -s http://httpbin.org/ip
{
"origin": "185.21.216.155"
}
qbittorrent advanced settings
- Network interface: lo
- Optional IP address to bind to: 185.21.216.155
... but libtorrent still ignores the lo interface
workaround
create a network namespace ("container") with an internal non-loopback network interface linked to the external loopback network interface
code
https://github.com/arvidn/libtorrent/blob/21cbbf74ee4cdf41c5877fddb84bde128d217914/docs/manual.rst?plain=1#L871-L883
code search: if_flags::loopback
https://github.com/arvidn/libtorrent/blob/21cbbf74ee4cdf41c5877fddb84bde128d217914/src/session_impl.cpp#L334
expected
libtorrent should
- use global addresses on local interfaces
- ignore local addresses like 127.0.0.1 or 10.0.0.1
so, is the issue that libtorrent incorrectly assumes that the LOOPBACK flag on the interface means it can only reach the local machine?
Does it work if you remove the || (ipface.flags & if_flags::loopback) check?
Does it work if you remove the
|| (ipface.flags & if_flags::loopback)check?
no, qbittorrent still says DHT: 0 nodes
https://github.com/arvidn/libtorrent/blob/21cbbf74ee4cdf41c5877fddb84bde128d217914/src/session_impl.cpp#L331-L338
with debug prints in session_impl.cpp i get
session_impl.cpp: ipface.name = lo
session_impl.cpp: ipface.interface_address = 127.0.0.1
session_impl.cpp: ipface.interface_address.is_loopback() = 1
session_impl.cpp: is_link_local(ipface.interface_address) = 0
session_impl.cpp: (ipface.flags & if_flags::loopback) = 4
session_impl.cpp: !is_global(ipface.interface_address) = 1
session_impl.cpp: !(ipface.flags & if_flags::pointopoint) = 1
session_impl.cpp: has_any_internet_route(routes) = 1
session_impl.cpp: !has_internet_route(ipface.name, family(ipface.interface_address), routes) = 0
session_impl.cpp: local = 1
session_impl.cpp: ipface.name = lo
session_impl.cpp: ipface.interface_address = 10.0.0.1
session_impl.cpp: ipface.interface_address.is_loopback() = 0
session_impl.cpp: is_link_local(ipface.interface_address) = 0
session_impl.cpp: (ipface.flags & if_flags::loopback) = 4
session_impl.cpp: !is_global(ipface.interface_address) = 1
session_impl.cpp: !(ipface.flags & if_flags::pointopoint) = 1
session_impl.cpp: has_any_internet_route(routes) = 1
session_impl.cpp: !has_internet_route(ipface.name, family(ipface.interface_address), routes) = 0
session_impl.cpp: local = 0
session_impl.cpp: ipface.name = lo
session_impl.cpp: ipface.interface_address = 185.21.216.155
session_impl.cpp: ipface.interface_address.is_loopback() = 0
session_impl.cpp: is_link_local(ipface.interface_address) = 0
session_impl.cpp: (ipface.flags & if_flags::loopback) = 4
session_impl.cpp: !is_global(ipface.interface_address) = 0
session_impl.cpp: !(ipface.flags & if_flags::pointopoint) = 1
session_impl.cpp: has_any_internet_route(routes) = 1
session_impl.cpp: !has_internet_route(ipface.name, family(ipface.interface_address), routes) = 0
session_impl.cpp: local = 0
185.21.216.155 is my public ip address
im trying to fix this, once again...
in my use-local-interfaces-2 branch i have a added some debug prints to src/session_impl.cpp and i have added a DHT test client in tools/dht_client_test.cpp
mkdir build && cd build
cmake -Dbuild_tools=on ..
make
./tools/dht_client_test --bind lo --btih a9ae5333b345d9c66ed09e2f72eef639dec5ad1d
as expected:
on my "good" server, the DHT test client connects to many DHT nodes
on my "bad" server, the DHT test client always says DHT nodes: 0
a minimal UDP echo test does work on my "bad" server
either binding to all IP addresses 0.0.0.0
or binding only to my public IP address $(curl ip.me)
udp-echo-test-boost-sync server --max-time 999 --addr 0.0.0.0
udp-echo-test-boost-sync server --max-time 999 --addr $(curl ip.me)
one problem is this line
if (m.addr.address() != i->second->target_addr()) continue;
in this part of libtorrent/src/kademlia/rpc_manager.cpp
bool rpc_manager::incoming(msg const& m, node_id* id)
{
// ...
observer_ptr o;
auto range = m_transactions.equal_range(tid);
for (auto i = range.first; i != range.second; ++i)
{
// FIXME
if (m.addr.address() != i->second->target_addr()) continue;
o = i->second;
i = m_transactions.erase(i);
break;
}
on my "bad" server all UDP response packets seem to come from my public IP address
so m.addr.address() is my public IP address
and i->second->target_addr() is the actual peer IP address
so observer_ptr o remains unset
so the incoming DHT message is dropped by
if (!o)
{
// ...
return false;
}
i have tried to fix this in fd283118c04e7e3cfd11c3c7cb52d5da487b7aab and 1b5f7c73c813b0d2831a6b47c8f8d1fb7b2500ee
by removing the line
if (m.addr.address() != i->second->target_addr()) continue;
and by replacing the wrong m.addr with
local_m_addr = i->second->target_ep();
but DHT is still not working on my "bad" server...
maybe i have to actually replace m.addr
but that would require passing a mutable (non-constant) reference of m to
bool rpc_manager::incoming(msg const& m, node_id* id)
i have added "a million debug prints" to the DHT handling code maybe someone can decode this mess...
dht_client_test.good.out.1760463461.gz dht_client_test.bad.out.1760514284.gz
what i dont understand:
my "bad" server receives much less nodes values
in traversal_observer::reply
$ cat dht_client_test.good.out.1760463461 | grep '^traversal_observer::reply look_for_nodes' | cut -c65- | tr ' ' $'\n' | sort -u | wc -l
197
$ cat dht_client_test.bad.out.1760514284 | grep '^traversal_observer::reply look_for_nodes' | cut -c65- | tr ' ' $'\n' | sort -u | wc -l
24
and my "bad" server receives absolutely no values values
in get_peers_observer::reply
$ cat dht_client_test.good.out.1760463461 | grep -E '\[get_peers_observer::reply\] got [0-9]+ peers in' | cut -d: -f4- | xargs -r echo | tr ' ' $'\n' | sort -u | wc -l
134
$ cat dht_client_test.bad.out.1760514284 | grep -E '\[get_peers_observer::reply\] got [0-9]+ peers in' | cut -d: -f4- | xargs -r echo | tr ' ' $'\n' | sort -u | wc -l
0
i do not have root access to my "bad" server
so i cannot use tools like tcpdump
but i can add more debug prints
the "public IP address assigned to the lo interface" network setup can be reproduced with linux containers (systemd-nspawn, docker, ...) AI prompt:
show me how to create a systemd-nspawn container on MY_DISTRO linux with my public IP address assigned to the "lo" interface, so that the container's "lo" interface has both my localhost IP address (127.0.0.1) and my public IP address (for example 192.168.178.21) (and maybe also the container's own IP address like 10.0.0.1)
so the output of
ip address show loshould look like this:$ ip address show lo 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet 10.0.0.1/32 scope global lo valid_lft forever preferred_lft forever inet 185.21.216.155/32 scope global lo