PurpleI2P / i2pd

🛡 I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website
BSD 3-Clause "New" or "Revised" License
3.26k stars 423 forks source link

Fix UPnP: error 2 #2094

Open self-related opened 2 months ago

self-related commented 2 months ago

Проблема: UPnP: Unable to find valid Internet Gateway Device: error 2

Подробнее: Предположительно, проблема в нестандартном IGD - https://github.com/dappnode/DNP_DAPPMANAGER/issues/312

Ручной форвард в miniupnpc выдает "Not connected IGD":

$ upnpc -r 11111 11111 UDP
upnpc : miniupnpc library test client, version .
...
List of UPNP devices found on the network :
 desc: http://192.168.31.1:5351/rootDesc.xml
 st: urn:schemas-upnp-org:device:InternetGatewayDevice:1

Found a (not connected?) IGD : http://192.168.31.1:5351/ctl/IPConn
No valid UPNP Internet Gateway Device found.

Однако с флагом -i (игнор) все корректно форвардится:

$ upnpc -i -r 11111 11111 UDP

upnpc : miniupnpc library test client, version .
...
List of UPNP devices found on the network :
 desc: http://192.168.31.1:5351/rootDesc.xml
 st: urn:schemas-upnp-org:device:InternetGatewayDevice:1

Found a (not connected?) IGD : http://192.168.31.1:5351/ctl/IPConn
Trying to continue anyway
Local LAN ip address : 192.168.31.*
ExternalIPAddress = *.*.*.*
InternalIP:Port = 192.168.31.*:11111
external *.*.*.*:11111 UDP is redirected to internal 192.168.31.*:11111 (duration=0)

В i2pd/daemon/UPnP.h случай с not connected соответствует: UPNP_IGD_VALID_NOT_CONNECTED = 2

Лог i2pd до фикса:

22:01:33@480/none - i2pd v2.53.1 (0.9.63) starting...
22:01:35@827/warn - Transports: 15 ephemeral keys generated at the time
22:01:35@106/error - UPnP: Unable to find valid Internet Gateway Device: error 2

После:

$ ./i2pd 
22:02:51@613/none - i2pd v2.53.1 (0.9.63) starting...
success022:02:54@33/warn - Transports: 15 ephemeral keys generated at the time
22:02:54@581/error - UPnP: Found Internet Gateway Device http://192.168.31.1:5351/ctl/IPConn

Форвард проходит.

Vort commented 2 months ago

Как я понимаю, роутер выдаёт Not connected когда считает, что у него нету прямого подключения к Интернету. Понятно, что у роутера могут быть ложные определения доступности сети, но если предположить, что в среднем у пользователей доступность чаще определяется верно, то такой патч позволит им пробрасывать порт из недоступной сети в недоступную. Точно не помню, какие от этого негативные последствия, но это как минимум неверно логически.

self-related commented 2 months ago

Что интересно, в Transmission порт пробрасывается всегда и там тоже используется miniupnc с проверкой только на UPNP_IGD_VALID_CONNECTED - https://github.com/transmission/transmission/blob/1e16912ae4a20ca338cfe89c72418cd37eac0102/libtransmission/port-forwarding-upnp.cc#L270

Я не разобрался, как у них это реализовано. Возможно ли такое решение?

Vort commented 2 months ago

Я не разобрался, как у них это реализовано.

Наверно, с этого места стоит изучать: https://github.com/transmission/transmission/pull/6718

self-related commented 2 months ago

Пришлось собрать transmission-daemon и добавить в дебаг вывод после проверки UPNP_IGD_VALID_CONNECTED - там тоже всегда статус 2 и он не изменяется. Спустя несколько попыток реконнекта у меня он просто забивает на статус, хотя в коде не совсем понял, при каком условии, и кажется там Fail вообще не обрабатывается. https://github.com/transmission/transmission/pull/6718/commits/2698cac9b0a0e7422a5ce2a03887d7dfcd6645c7

        if (
            UPNP_GetValidIGD(devlist, &handle->urls, &handle->data, std::data(lanaddr), std::size(lanaddr) - 1, nullptr, 0)
            == UPNP_IGD_VALID_CONNECTED)
        {
            ...
        }
        else
        {
            handle->state = UpnpState::WillDiscover;  //вот здесь новая попытка
            ...
        }
enum class UpnpState : uint8_t
{
    Idle,
    WillDiscover, // next action is upnpDiscover()
inf port-forwarding.cc:216 State changed from 'Starting' to 'Not forwarded' (port-forwarding.cc:216)
inf port-forwarding.cc:216 State changed from 'Not forwarded' to 'Starting' (port-forwarding.cc:216)

Да, там все странно - отключил сейчас upnp на роутере и transmission бесконечно сыпет в лог уже больше получаса:

dbg port-forwarding-natpmp.cc:46 readnatpmpresponseorretry failed. Natpmp returned -7 (the gateway does not support nat-pmp); errno is 111 (Connection refused) (port-forwarding-natpmp.cc:46)
inf port-forwarding.cc:216 State changed from 'Starting' to 'Not forwarded' (port-forwarding.cc:216)
inf port-forwarding.cc:216 State changed from 'Not forwarded' to 'Starting' (port-forwarding.cc:216)

Возможно, проблема с пробросом в самом miniupnpc. Например qbittorrent судя по логам пробрасывает сразу же и без ошибок.

Vort commented 2 months ago

Возможно, проблема с пробросом в самом miniupnpc. Например qbittorrent судя по логам пробрасывает сразу же и без ошибок.

miniupnpc использует команду GetStatusInfo для проверки статуса: https://github.com/miniupnp/miniupnp/blob/d07b0a1a9d4ce62f36040d64a4b629a914950f9c/miniupnpc/src/miniupnpc.c#L494-L501

В libtorrent я вообще фразы GetStatusInfo не нахожу. Видимо, ему всё равно.

Вот тут есть кое какое упоминание этой команды: http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v2-Device.pdf

Vort commented 2 months ago

Интересно. Оказывается, в новой версии miniupnpc другие константы. Как пользователям библиотеки предполагается об этом узнавать?

Правда, дополнительный код - он всё равно "не единица".

https://github.com/miniupnp/miniupnp/blob/d07b0a1a9d4ce62f36040d64a4b629a914950f9c/miniupnpc/src/miniupnpc.c#L505-L520

self-related commented 2 months ago

Хм, на версии 2.2.8 клиент upnpc корректно пробрасывает сразу без флага -i. I2pd и transmission по прежнему отбрасывают state 2 .

ОДНАКО вот что я заметил в miniupnpc: https://github.com/miniupnp/miniupnp/commit/c0a50ce33e3b99ce8a96fd43049bb5b53ffac62f#diff-f4321c7c8d1e7ee67ca583dcc83ef3e391e233aa97c502fca50b32535d9cf936R624

Было:

/* in state 2 and 3 we don't test if device is connected ! */
if(state >= 2)

Стало:

/* in state 3 and 4 we don't test if device is connected ! */
if(state >= 3)

Видимо, в этом и суть новых констант, теперь state 2 считается приемлемым? Если так, то патч к i2pd это и делает, только тогда надо еще добавить в условие API 18, чтобы было корректно

Vort commented 2 months ago

I2pd и transmission по прежнему отбрасывают state 2 .

Отбрасывают не единицу.

Видимо, в этом и суть новых констант, теперь state 2 считается приемлемым?

Как я понял, состояние 2 теперь разбито на состояние 2 и 3. Но это по-прежнему не единица.

Может, ещё какое-то изменение есть.

Было: Стало:

Я это понимаю так: раз дошли до состояния 2 (или 3), то всё плохо и уже мелкие проблемы проверять смысла нету.

self-related commented 2 months ago

раз дошли до состояния 2 (или 3), то всё плохо

Но теперь это 3 и 4 в новой версии, а статус 2 проверяется вместе со статусом 1 по-умолчанию

Добавил вывод статуса в конец UPNP_GetValidIGD printf("final state: %d\n", state);

Теперь вывод такой:

$ ./build/upnpc-static -r 12345 12345 udp
upnpc: miniupnpc library test client, version 2.2.8.
 (c) 2005-2024 Thomas Bernard.
More information at https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/

List of UPNP devices found on the network :
 desc: http://192.168.31.1:5351/rootDesc.xml
 st: urn:schemas-upnp-org:device:InternetGatewayDevice:1

final state: 2
Found an IGD with a reserved IP address (*.*.*.*) : http://192.168.31.1:5351/ctl/IPConn
Local LAN ip address : 192.168.31.*
ExternalIPAddress = *.*.*.*
InternalIP:Port = 192.168.31.*:12345
external *.*.*.*:12345 UDP is redirected to internal 192.168.31.*:12345 (duration=0)

Т.е. статус 2 теперь не not connected, а reserved ip и проходит по умолчанию (место звездочек в первом случае тоже внешний айпи).

Vort commented 2 months ago

Но теперь это 3 и 4 в новой версии, а статус 2 проверяется вместе со статусом 1 по-умолчанию

Только это касается не библиотеки, а утилиты. Вот "правильная" строчка: https://github.com/miniupnp/miniupnp/blob/d07b0a1a9d4ce62f36040d64a4b629a914950f9c/miniupnpc/src/upnpc.c#L757

Кстати, они таки добавили константы.

Т.е. статус 2 теперь не not connected, а reserved ip и проходит по умолчанию

Так всё же - зачем i2pd нужен проброс с reserved ip? i2pd ведь белый ip нужен, доступный из Интернета.

Если же miniupnpc или железка отмечают белый IP как серый, то надо разбираться, почему.

self-related commented 2 months ago

i2pd ведь белый ip нужен

За nat ведь тоже можно получать прямые udp соединения. У меня на джава роутере upnp работал и показывал прямые SSU соединения в статистике. Соответственно, порт надо открыть, особенно, когда он меняется с каждым рестартом

Vort commented 2 months ago

"Пробитие" NAT к UPnP, насколько я знаю, не относится. Этим занимаются отдельные механизмы внутри i2pd (интродьюсеры).