[к списку]

SOCKS-прокси

  1. Необходимо реализовать учебное подмножество протокола SOCKS версии 5 (RFC 1928) с ограничениями, указанными ниже.
  2. В параметрах программе передаётся только порт, на котором прокси будет ждать входящих подключений от клиентов.
  3. Из трёх доступных в протоколе команд, обязательной является только реализация команды 1 (establish a TCP/IP stream connection)
  4. Поддержку методов аутентификации GSSAPI и USERNAME/PASSWORD, а также IPv6-адресов реализовывать не требуется. При этом начальное согласование метода обязательно: сервер выбирает метод 0x00 (NO AUTHENTICATION REQUIRED), если клиент его предложил; иначе отвечает 0xFF и закрывает соединение. Эти ограничения означают, что полное соответствие RFC 1928 не требуется.
  5. Для реализации прокси использовать неблокирующиеся сокеты, работая с ними в рамках одного треда. Дополнительные треды использовать не допускается. Соответственно, никаких блокирующихся вызовов (кроме вызова селектора) не допускается.
  6. Прокси не должна делать предположений о том, какой протокол уровня приложений будет использоваться внутри перенаправляемого TCP-соединения. В частности, должна поддерживаться передача данных одновременно в обе стороны, а соединения должны закрываться аккуратно (только после того, как они больше не нужны).
  7. В приложении не должно быть активного ожидания (busy waiting): если прогресс невозможен, цикл должен ждать готовности сокетов или ближайшего таймера через селектор. Отдельная итерация может не передавать данные, например при обработке нового соединения, завершения соединения, ошибки или тайм-аута. Нельзя постоянно отслеживать готовность к записи, когда нет данных для отправки.
  8. Не допускается неограниченное расходование памяти для обслуживания одного клиента.
  9. Производительность работы через прокси не должна быть заметно хуже, чем без прокси. Для отслеживания корректности и скорости работы можно глядеть в Developer tools браузера на вкладку Network.
  10. Прокси должен поддерживать резолвинг доменных имён (значение 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-браузер на использование вашего прокси, и посещать любые веб-сайты, богатые контентом.

Описание протокола:
  1. На английской Википедии
  2. SOCKS 5 RFC
  3. SOCKS для самых маленьких
Баллов за задачу: 3