[к списку]
SOCKS-прокси
- Необходимо реализовать учебное подмножество протокола SOCKS версии 5 (RFC 1928) с ограничениями, указанными ниже.
- В параметрах программе передаётся только порт, на котором прокси будет ждать входящих подключений от клиентов.
- Из трёх доступных в протоколе команд, обязательной является только реализация команды
1 (establish a TCP/IP stream connection)
- Поддержку методов аутентификации GSSAPI и USERNAME/PASSWORD, а также IPv6-адресов реализовывать не требуется. При этом начальное согласование метода обязательно: сервер выбирает метод
0x00 (NO AUTHENTICATION REQUIRED), если клиент его предложил; иначе отвечает 0xFF и закрывает соединение. Эти ограничения означают, что полное соответствие RFC 1928 не требуется.
- Для реализации прокси использовать неблокирующиеся сокеты, работая с ними в рамках одного треда. Дополнительные треды использовать не допускается. Соответственно, никаких блокирующихся вызовов (кроме вызова селектора) не допускается.
- Прокси не должна делать предположений о том, какой протокол уровня приложений будет использоваться внутри перенаправляемого TCP-соединения. В частности, должна поддерживаться передача данных одновременно в обе стороны, а соединения должны закрываться аккуратно (только после того, как они больше не нужны).
- В приложении не должно быть активного ожидания (busy waiting): если прогресс невозможен, цикл должен ждать готовности сокетов или ближайшего таймера через селектор. Отдельная итерация может не передавать данные, например при обработке нового соединения, завершения соединения, ошибки или тайм-аута. Нельзя постоянно отслеживать готовность к записи, когда нет данных для отправки.
- Не допускается неограниченное расходование памяти для обслуживания одного клиента.
- Производительность работы через прокси не должна быть заметно хуже, чем без прокси. Для отслеживания корректности и скорости работы можно глядеть в Developer tools браузера на вкладку Network.
- Прокси должен поддерживать резолвинг доменных имён (значение
0x03 в поле ATYP; само доменное имя передаётся в поле DST.ADDR). Резолвинг тоже должен быть неблокирующимся. Для этого предлагается использовать следующий подход:
- На старте программы создать новый UDP-сокет и добавить его в селектор на чтение
- Когда необходимо отрезолвить доменное имя, отправлять через этот сокет DNS-запрос A-записи на адрес рекурсивного DNS-резолвера
- В обработчике чтения из сокета обрабатывать ответ на DNS-запрос и продолжать работу с полученным адресом. Поддержка DNS поверх TCP, в том числе повтор запроса по TCP при усечённом UDP-ответе (флаг
TC=1), в этой задаче не требуется. Усечённый ответ, из которого нельзя получить адрес, обрабатывать как ошибку резолвинга.
- Если ответ на DNS-запрос не пришёл за заданный конечный тайм-аут, повторить запрос. Выполнить не более трёх попыток всего (первоначальный запрос и два повтора). Ожидание тайм-аутов и повторы должны быть неблокирующимися, через цикл селектора, без активного ожидания. Если после третьей попытки ответ так и не пришёл, отправить клиенту ответ SOCKS5 с кодом ошибки
REP=0x04 (Host unreachable) и закрыть соединение с ним; остальные клиенты должны продолжать обслуживаться.
Для получения адреса рекурсивного резолвера, а также для формирования и парсинга DNS-сообщений на Java предлагается использовать библиотеку dnsjava (для других языков найдите сами).
Для тестирования можно настроить любой Web-браузер на использование вашего прокси, и посещать любые веб-сайты, богатые контентом.
Баллов за задачу: 3