Коварный роутер или почему порты надо открывать
В статье небольшая история о том как желание упростить приложение для конечного пользователя, вышло весьма трудоемким процессом.
Речь об «автоматической» пробросе порта, через технологию UPnP, без использования «стандартной» библиотеки NATUPnPLib.
О том, в силу чего был выбран такой непростой путь и почему он все-таки непростой — читайте ниже.
ПрологРаботая над своим проектом (игровой проект), я четко осознавал, что рано или поздно придется подойти в плотную к вопросу сервера и связки с оным. Стоит отметить, что он был в планах. Но я помнил свой опыт работы с выделенным сервером, для того же minecraft'a, был готов к тому что меня ждет определенная «боль», в особенности, если я попытаюсь продвинуть свой продукт в массы.
- Пользователь должен делать минимум движений для запуска сервера и начала игры.
- Решение должно работать «из коробки» на любой машине под Windows (поддержка Unix систем пока что не в планах).
- Проброс порта должен происходить без вмешательства человека.
- Решение будет написано на C# в двух архитектрурах x64 и x32 (приоритет на x64, в связи с тем, что вероятно потребуется «много» памяти).
Определившись с языком, первым же делом отправился в гугл, узнать если ли уже готовые решения. И действительно, было сразу же найдено «решение», предлагалось использовать библиотеку NATUPnPLib, и сразу же пример ее использования:
Возрадовавшись сему, я поспешил опробовать полученную «зверюшку». В конечном итоге на одной машине мне удалось, получить желаемый результат. Однако, когда я уже совсем возрадовался, я решил «чуток потестировать» (вообще как показывает практика, это полезное действие), и запустил скомпиленый код на соседней машине (в одной локалке, с одним роутером «во главе») и тут меня ждало первое разочарование.
Увы, upnpnat.StaticPortMappingCollection — возвращал null, что бы я не делал. Вместе с этим пришел «отчет» от другого человека, которого я так же попросил протестировать, его ответ был так же грустен, у него данная библиотека вообще не разрешалась (вероятно была не зарегистрирована в системе, по какой-то причине или запрещена, или еще как, но суть в том что она не подхватывалась).
«Отсутствие результата, тоже результат» — так гласит одна хитрая мудрость. Печальный результат, дал мне понимание, что если я оставлю эту библиотеку, то подобные же ошибки будут у конечного потребителя, а значит мне придется готовится принимать поток «добра». Что мне, почему-то совсем не хотелось.
Поиск путиВ расстроенных чувствах пошел гуглить замену NATUPnPLib. Но как оказалось, почти все готовые решения были по сути обертками над этой библиотекой. Ради эксперимента они были попробованы, но поскольку в основе NATUPnPLib, то все заканчивалось как и ранее.
Это сильно огорчало. Однако глядя на такие продукты как например uTorrent, и видя что он успешно пробрасывает порт, я решил пойти научным хитрым путем. Идея проста как котик. Я знаю, что от машины до роутера передается некая команда или часть, в которой должно быть как-то сказано, что роутер должен сделать. Оставалось дело за малым, «посмотреть в лицо» этой команде или набору команд.
Первой же ссылкой в гугле, на запрос о сетевом сниффере был Wireshark. Дальше начав гуглить, что он умеет попалась вот эта статья от товарища sinist3r за что ему большое спасибо. В статье в достаточной степени описано, как и что делать, потому подробно на этом останавливаться не стану.
Анализ сетевого трафика- Кучу сетевой информации
- Знаем ip адрес машины, с которой отправляем
- Знаем ip адрес других машин
Смотрим на полученный список и видим какую-то интересную вещь, а именно мультикаст группа и протокол SSDP, и на нее посылается такое сообщение:
Радостно потираю лапки, как муха, из буквально маленькой статьи на Википедии, узнаю, что используется протокол UDP, посылается сообщение обнаружение, которое было приведено в скрине выше. И все говорит, о том что я иду правильным путем.
От теории к практикеИмея под рукой запрос, протокол по которому обращаемся и адрес, я решил попробовать сделать небольшую консольную программку, для того что бы протестеровать, как оно будет, и будет ли вообще, работать.
Итак, такой запрос надо отправить в мультикаст группу, на порт 1900. Роутер услышав запрос, ответит, машине с которой пришел запрос, с неким ответом.
Подробнее, что есть что, можно посмотреть тут.
Пишем примерно такой код:
Почему использую такую конструкцию:
А не IPAddress.Any, в следствии вот этого ответа
Теперь не много о функции GetLocalAdress. Обычно, предлагают использовать такой или подобный код
Однако, если у вас на машине стоит VirtualBox или скажем Tunngle, или что-то подобное, что ставит свой адаптер, то в таком случае, указанный выше код, вернет адрес этого самого адаптера. Что «не есть хорошо», и потому надо либо как-то по названиям пытаться обрезать «левые» адреса, либо как предлагаю я:
Конец уж близокИтак, почти все у нас есть. Можно перейти к финальной стадии, а именно, попробовать на практике пробросить порт и его закрыть.
Опущу моменты как я в Wireshark'e, наблюдал какие команды ходят и куда, ранее достаточно подробно писал, дальнейший поиск достаточно прост, учитывая, что все общение с роутером уже идет через HTTP.
Получив на предыдущей стадии, путь для запроса информации о роутере, сделаем это. Сразу оговорюсь, при HTTP запросах, обязательно указывать UserAgent = «Microsoft-Windows/6.1 UpnP/1.0»; (естесвенно учитывая реальную версию Windows).
В моем случае GET-запрос, надо послать по этому адресу:
В полученном, огромном ответе, (да-да, вот в этой огромной простыне текста), нас интересует тег controlURL, у которого serviceType равен urn:schemas-upnp-org:service:WANIPConnection:1.
В моем случае получено значение «/ctl/IPConn». Дописываем его к адресу роутера, в итоге получаем такое:
- NewRemoteHost //оставляем пустым
- NewExternalPort //внешний порт
- NewProtocol //протокол (TCP/UDP)
- NewInternalPort //внутренний порт
- NewInternalClient //ip «на который» открываем
- NewEnabled //включен или выключен
- NewPortMappingDescription //описание
- NewLeaseDuration //продолжительность жизни, 0 — навсегда
Аналогично, но проще делается для удаления порта, там всего два важных параметра — это протокол и порт, подчеркну, внешний порт.
ЗаключениеВот так вот, простое желание сделать для пользователя «проще», вылилось в целую эпопею и статью. Надеюсь, статья кому-либо поможет избежать тех определенных трудностей, с которыми я столкнулся.
Всем спасибо, кто прочитал, если имеются какие-то дополнения, пишите, дополню статью.