Что такое SOCKS?

SOCKS - это протокол проксирования (посредования) соединений между клиентом и сервером, который призван обеспечить максимальную прозрачность для приложений. SOCKS в целом разрабатывался с целью обеспечения контролируемых соединений между хостами в приватной сети, которые находятся за фаерволом, и хостами в публичной сети Интернет. В настоящее время используемая версия протокола - это SOCKS версии 5, которая была разработана в 1996 году, и спецификация которой представлена в RFC1928. SOCKS был популярен как метод выхода в “большую сеть” до нынешнего NAT. Если сравнивать SOCKS с NAT, то можно отметить следующие моменты:

  • SOCKS работает как “прослойка” между приложением и траспортом, и на модели OSI его принято ставить на сессионный уровень. Отсюда возникает необходимость, чтобы само приложение поддерживало SOCKS, что может быть далеко не всегда. А еще возникает проблема распространения настроек SOCKS-сервера на хосты, что часто делается просто ручным вводом в приложение. В NAT такой проблемы не возникает. У хоста есть IP, шлюз по умолчанию и DNS-сервер (которые чаще всего приходят по DHCP), и для него взаимодейсвие с сетью Интернет прозрачно. Всей работой по трансляции адресов и удержанием сессий занимается устройство на периметре внутренней сети (обычно роутер или фаервол).

  • Но с другой стороны, SOCKS, так сказать, никого не обманывает. Когда устанавливается SOCKS-сессия, то SOCKS-прокси сообщает клиенту внешний IP-адрес и порт, который закреплен за сессией клиента. Это открывает возможность таким протоколам как FTP или SIP, которые содержат в теле своих пакетов инфомацию о IP-адресации, подставлять правильные данные сразу на выходе от клиента, без трансформации пакета устройствами “по пути”. Это также позволяет более простую эксплуатацию p2p-приложений, и стоит упомянуть поддержку со стороны SOCKS “реверс-прокси” и протокола UDP. В NAT чаще всего для такого включаются дополнительные фичи “сверху” основных функций, которые лезут в пакеты уровня приложения и трансформируют их. В вопросе p2p-приложений в случае маленькой сети проблема решается протоколом UPnP, который может не всегда поддерживаться периметровым устройством или самим приложением. А в жизни часто требуется ручной проброс портов.

  • С некоторой точки зрения, SOCKS обеспечивает больший контроль за трафиком (из-за самого факта разрыва соединения), а также поддерживает аутентификацию пользователя. В случае NAT контроль обеспечивается меньший, и нет аутентификации (если без применения дополнительных методов).

Но NAT естественным образом победил - несмотря на некоторые недостатки, эксплуатировать его оказалось гораздо легче, чем SOCKS, да и работает NAT быстрее.

После распространения NAT, SOCKS используется чаще как средство сокрытия реальных IP-адресов, обхода ограничений доступа в Интернет, а также как удобный транспорт для состыкования приложений и оверлейных сетей, таких как TOR и I2P. Все браузеры и многие IM-приложения имеют в своем распоряжении SOCKS-клиент.

Какие недостатки имеет SOCKS с точки зрения проксирования? Как минимум - SOCKS, в большинстве реализаций, не шифрует проксируемый трафик. Конечно, можно сказать, что поддержка шифрования - задача самого приложения или “прослойки” уровня выше, что чаще всего TLS/SSL. Это, в целом, правильно, но тут возникает другая проблема - проблема идентификации трафика средствами DPI (как самого SOCKS, так и проксируемых приложений), что может вести к ограничению применения SOCKS в некоторых сетях. И все-таки отсутствие универсальности шифрования для трафика, в том случае, если прокси используется как метод доступа к внутренним ресурсам приватной сети (если применение VPN невозможно или неоптимально по каким-либо причинам). И более того - при использовании метода аутентификации 0x02 “логин/пароль”, эти логин/пароль улетают на сервер в виде открытого текста, что вызывает риск их перехвата. Для правоты стоит сказать, что для безопасной аутентификации и защиты трафика, SOCKS поддерживает GSSAPI. Только вот GSSAPI штука довольно старая и сложная в настройке, и я, честно говоря, пока что не видел его поддержки в каком-либо приложении при настройке SOCKS, кроме curl.

Как же можно подключаться к прокси с шифрованием? Например, использовать S… SSH!

SSH-тоннели

SSH, как известно, поддерживает проксирование TCP-трафика “внутри” текущего SSH-соединения. Таких типов проксирования три:

  1. Local Port Forwarding
  2. Remote Port Forwarding
  3. Dynamic Port Forwarding

Первые 2 рассматривать в данной статье я не буду, но остановлю внимание на типе Dynamic Port Forwarding. SSH Dynamic Port Forwarding позволяет сделать проксирование SOCKS-over-SSH. Работает это так - при соединении SSH-клиента и SSH-сервера со стороны SSH-клиента поднимается SOCKS-прокси, например, на localhost’е, на который можно указывать приложениям с поддержкой SOCKS. Само проксирование будет через SSH-сервер, с которым вы соединяетесь. В сумме - Интернет вас будет видеть от имени SSH-сервера, соединение между SSH-клиентом и SSH-сервером зашифровано, так что не видно вложенных данных приложения, а для приложения все выглядит как обращение к обычному SOCKS-прокси.

socks-over-ssh

На Linux выполняется это очень просто:

ssh -D <адрес локального интерфейса>:<порт socks> <пользователь>@<ssh-сервер>

Чтобы поднять SOCKS-прокси на локалхосте, обычно делают так:

ssh -D 1080 <пользователь>@<ssh-сервер>

Приложению остается указать SOCKS-сервер localhost (127.0.0.1) и порт 1080, без аутентификации. Например, в Firefox вот так:

ff-socks-over-ssh
Хороший тон - это поставить проксирование DNS-запросов!

А еще лучше для большего контроля и удобства рекомендую использовать плагин FoxyProxy, использование которого описано в статье “Настройка универсального узла связи для выхода в оверлейные сети” (см. раздел по настройке I2P)

Чтобы расшарить прокси во внешнюю сеть, например устройствам в локальной сети, можно поставить адрес внешнего интерфейса:

ssh -D 192.168.1.100:1080 <пользователь>@<ssh-сервер>

Устройствам в локальной сети и самой локальной машине нужно будет обращаться к 192.168.1.100 в данном случае. Только не забудьте настроить локальный фаерволл, если он есть, так как он может блокировать обращения извне.

Работает эта фишка и на Windows - достаточно настроить клиент PuTTY. На Andoid поддержка есть в JuiceSSH, но только в платной версии. Хотя возможно есть аналоги.

Также стоит рассмотреть фичу SSH “PermitTunnel”, которая позволяет поднять полноценный L3-тоннель. Описана фича, например, тут. Но многие жалуются, что из-за TCP-over-TCP сильно проседает производительность, и рекомендуется как временное решение.

Какие же недостатки у SSH Dynamic Port Forwarding?

  1. SSH будет плохо работать в условиях мобильной сети - потребуются постоянные ручные реконнекты;

  2. SSH легко идентифицируется средствами DPI;

  3. Видел информацию, что SSH-прокси развивает довольно маленькую скорость доступа, но сам я не проверял;

  4. Для пользователей, плохо знакомых с командной строкой и SSH, может быть достаточно сложно;

  5. По моему есть проблема разграничения доступа. Хотелось бы некоторым пользователям давать доступ к прокси и только к прокси, выдав некоторые простые настройки (IP, порт, пароль). В случае SSH потребуется настройка разграничений доступа по пользователям и настройка SSH-сервера.

И вот на этом моменте перейдем к рассмотру такого инструмента, как Shadowsocks.

Shadowsocks

A secure socks5 proxy, designed to protect your Internet traffic.

Так кратко описывает инструмент веб-страничка проекта shadowsocks.org. Какими же преимуществами нам представляют shadowsocks разработчики данного проекта?

1) Super Fast. Bleeding edge techniques using Asynchronous I/O and Event-driven programming.

Отмечу, что в момент нагрузки ни сервер ни клиент много ресурсов не потребляют:

shsocks-cpu
Нагрузка на сервере

shsocks-cpu-client
Нагрузка на клиенте

Насчет скорости. По speedtest.net существенной просадки по скорости относительно обычного SOCKS5-прокси danted не обнаружил.

2) Flexible Encryption. Secured with industry level encryption algorithm. Flexible to support custom algorithms.

Ну, список алгоритмов стандартной поставки действительно немаленький:

Encrypt method: rc4-md5, 
aes-128-gcm, aes-192-gcm, aes-256-gcm,
aes-128-cfb, aes-192-cfb, aes-256-cfb,
aes-128-ctr, aes-192-ctr, aes-256-ctr,
camellia-128-cfb, camellia-192-cfb,
camellia-256-cfb, bf-cfb,
chacha20-ietf-poly1305,
xchacha20-ietf-poly1305,
salsa20, chacha20 and chacha20-ietf.
The default cipher is chacha20-ietf-poly1305.

Такой список алгоритмов поддерживается с целью подбора оптимального под большее число устройств. Например, для процессоров без аппаратной поддержки AES может быть более оптимален chacha20, и наоборот. Вообще, шифрование тут больше не для сокрытия трафика, а для обхода средств DPI. Подробнее об этом в следующем пункте, а об обфускации трафика в следующем разделе.

3) Mobile Ready. Optimized for mobile device and wireless network, without any keep-alive connections.

Shadowsocks, в отличие от SSH, не обязательно удерживать текущее TCP-соединение. Вы можете спокойно переходить из одной сети в другую (из сотовой в WiFi и обратно, менять WiFi-сеть) и при этом ручного переподключения или переподключения по тайм-ауту не требуется. На каждое новое соединение shadowsocks открывает новую TCP-сессию, также как и обычный SOCKS. Первоначальная установка соединения, аутентификация, согласование параметров - этого в shadowsocks не требуется вовсе. Данные, главным образом, шифруются симметричным ключом, который указывается в конфиге на стороне сервера и клиента. Клиент “вслепую” шифрует данные ключом, указанным в конфиге и посылает серверу, без всякого согласования. И если ключ не совпадает и shadowsocks-сервер не может расшифровать данные - сервер просто сбросит соединение, ответив RST-пакетом. Да, ну и можно упомянуть, что один ключ может работать с любым количеством устройств - никакой конкуренции между устройствами нет.

4) Cross Platform. Available on most platforms, including Windows, Linux, Mac, Android, iOS, and OpenWRT.

Не брешут. Клиентов и серверов действительно большое разнообразие. Linux, Windows, Android - имеются. Есть даже на OpenWRT.

5) Open Source. Totally free and open source. A worldwide community devoted to deliver bug-free code and long-term support.

Опенсурс. Что еще добавить?

6) Easy Deployment. Easy deployment with pip, aur, freshports and many other package manager systems.

Развертка действительно очень простая. По крайней мере, я настраивал быстрее, чем danted. Покажу на примере Ubuntu Server 16.04. Для начала несколько слов о работе. Принцип примерно такой же, как и у SSH - на клиентской машине поднимается локальный SOCKS-прокси, на который ссылаются приложения, а shadowsocks обеспечивает шифрованный транспорт между клиентом и прокси-сервером.

socks-over-shsocks
Заметьте, что shadowsocks тут не “стрелочка”, а просто “труба”, потому что shadowsocks своих собственных поверхностных соединений, которые требуется удерживать, в отличие от SSH, не устанавливает.

Устанавливаем из стандартного репо shadowsocks на С - shadowsocks-libev:

# apt install shadowsocks-libev

Открываем конфиг файл по умолчанию /etc/shadowsocks-libev/config.json и задаем несколько параметров

{
    "server":"123.123.123.123",
    "server_port":12345,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb"
}

По порядку:
server - внешний адрес, который будет прослушиваться сервером;
server_port - внешний порт, который будет прослушиватся сервером;
password - общий ключ-пароль между клиентом и сервером;
method - алгоритм шифрования.

Перезапускаем сервер:

systemctl restart shadowsocks-libev.service

И все, на этом закончили c сервером. Переходим к клиенту.

Устанавливаем на машине-клиенте тот же пакет:

# apt install shadowsocks-libev

Делаем конфиг-файл, например в домашней директории - ~/shsocks.json:

{
    "server":"123.123.123.123",
    "server_port":12345,
    "local_port":1080,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb"
}

Единственное отличие - это наличие local_port. Настройка указывает, какой порт на локалхосте будет использовать локальный SOCKS-сервер.

Запускаем:

ss-local -c ~/shsocks.json

Теперь можем увидеть, что на локалхосте на указанном порту поднялся наш локальный SOCKS-сервер:

~$ sudo ss -tlpn | grep "ss-local" 
LISTEN   0         128               127.0.0.1:1080            0.0.0.0:*        users:(("ss-local",pid=24855,fd=5))

Приложениям, проксируемым через SOCKS, указываем SOCKS-прокси с адресом 127.0.0.1 и портом из local_port - 1080. Для FF например нужно задать настройки как на скриншоте выше. ss-local при желании можно например загнать в cron или в штатный для системы автостарт, чтобы он сразу стартовал при запуске машины. Быстро проверить работу прокси можно например вот так:

curl --socks5 127.0.0.1:1080 ident.me

Теперь насчет мобильных девайсов. Я использую бета-версию Shadowsocks на Android. Удобные фичи - несколько режимов работы (прокси, VPN, transproxy), проксирование отдельных приложений, чтение конфига сервера из QR-кода. По последнему - насколько я знаю, генерировать QR можно в GUI-версиях Shadowsocks, а можно и такой простой командой:

pip install qr
echo -n "ss://"`echo -n aes-256-cfb:password@123.123.123.123:12345 | base64 -w 0` | qr

Только подставьте свои алгоритм, пароль, IP и порт сервера.

SOCKS можно расшарить во внешнюю сеть, просто привязав SOCKS-сервер на клиентской машине к адресу внешнего интерфейса. Удобно, если вы запускаете shadowsocks на сервере в своей локальной сети.

~$ ss-local -c ~/shsocks.json -b 192.168.1.100 -l 1080

Обфускация трафика

Shadowsocks предоставляет возможность подключить плагин обфускации трафика simple-obfs. Плагин маскирует прокси-трафик под HTTP или TLS/SSL, чтобы эффективно обходить системы DPI. Для этого нужно внести всего несколько настроек на стороне сервера и клиента.

В первую очередь на обеих сторонах нужно установить плагин:

apt install simple-obfs

Второй момент - чтобы разрешить прослушивать серверу ss-server порты <1024 без запуска из-под root, нужно выполнить следующую команду:

sudo setcap 'cap_net_bind_service=+ep' /usr/bin/ss-server

Начнем с маскировки под HTTP. Для маскировки под HTTP на стороне сервера дополним конфиг /etc/shadowsocks-libev/config.json:

{
    "server":"123.123.123.123",
    "server_port":80,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb",
    "plugin":"obfs-server",
    "plugin_opts":"obfs=http;failover=204.79.197.200:80"
}

Конфиг дополнен строками:
server_port - так как маскируемся под HTTP, то ставим порт 80;
plugin - подключение плагина simple-obfs, и именно серверную его часть obfs-server;
plugin_opts - опции плагина, из которых:
obfs=http - задает маскировку трафика под HTTP;
failover=204.79.197.200:80 - проксирование запроса на заданный сервер, если на прокси-сервер придет HTTP-запрос не-simple-obfs (обычный HTTP-запрос). Сделано это для дополнительной маскировки вашего прокси-сервера, и может помочь при автоматизированной проверке прокси-сервера на наличие правильно сформированного HTTP-ответа. В данном случае маскируемся под bing.com, не забываем этот момент - о нем будет упоминание далее.

После перезапускаем сервер:

systemctl restart shadowsocks-libev.service

Теперь конфиг со стороны клиента, например создаем файл ~/shsocks-obfs-http.json:

{
    "server":"123.123.123.123",
    "server_port":80,
    "local_port":1080,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb",
    "plugin":"obfs-local",
    "plugin_opts":"obfs=http;obfs-host=www.bing.com"
}

Конфиг дополнен строками:
server_port - на забываем поставить порт как в конфиге сервера - 80;
plugin - подключение плагина simple-obfs, и именно клиентскую его часть obfs-local;
plugin_opts - опции плагина, из которых:
obfs=http - задает маскировку трафика под HTTP;
obfs-host=www.bing.com - обращение к прокси будет выглядеть как GET-запрос к хосту www.bing.com. Поэтому, прежде в конфиге сервера мы указывали его IP-адрес в опции failover=204.79.197.200:80. Хотя DNS-запись указывает, что bing.com находится на адресе 204.79.197.200, обращение по HTTP к адресу прокси сервера ("server":"123.123.123.123"), если это не специально сформированный пакет simple-obfs, вернет всю туже самую страничку www.bing.com. В данном случае подозрение вызывает несоответствие DNS-записи, которая не указывает на наш прокси-сервер.

Запускаем клиента, проверяем работу прокси:

ss-local -c ~/shsocks-obfs-http.json
curl --socks5 127.0.0.1:1080 ident.me

Прокси-трафик теперь будет выглядеть как обычный обмен по HTTP. Для сравнения, сверху - замаскированный под HTTP прокси-трафик, внизу - настоящий запрос с помощью curl: shsocks-http-proxy
shsocks-http-real
Cверху - замаскированный под HTTP прокси-трафик, внизу - настоящий запрос с помощью curl

Теперь замаскируем прокси-трафик под TLS/SSL, что будет более приближено к жизни, нежели HTTP. На стороне сервера меняется следующее:

{
    "server":"123.123.123.123",
    "server_port":443,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb",
    "plugin":"obfs-server",
    "plugin_opts":"obfs=tls;failover=204.79.197.200:443"
}

Конфиг дополнен строками:
server_port - маскируемся под HTTPS, то ставим порт 443;
obfs=tls - задает маскировку трафика под TLS/SSL;
failover=204.79.197.200:443 - аналогично тому, что ставили прежде в этой опции при маскировке под HTTP. Не забываем про порт - 443.

Перезапускаем сервер:

systemctl restart shadowsocks-libev.service

На стороне клиента меняется следующее (сделаем например еще один файл ~/shsocks-obfs-tls.json):

{
    "server":"123.123.123.123",
    "server_port":443,
    "local_port":1080,
    "password":"password",
    "timeout":60,
    "method":"aes-256-cfb",
    "plugin":"obfs-local",
    "plugin_opts":"obfs=tls,obfs-host=www.bing.com"
}

Конфиг дополнен строками:
server_port - на забываем поставить порт как в конфиге сервера - 443;
obfs=tls - задает маскировку трафика под HTTPS-SSL/TLS;
obfs-host=www.bing.com - запросы TLS будут к указанному хосту;

Запускаем клиент, проверяем работу прокси:

ss-local -c ~/shsocks-obfs-tls.json
curl --socks5 127.0.0.1:1080 ident.me

Если залезть в дамп трафика, то вот например “Client Hello” обфусцированного трафика: shsocks-tls-proxy

Теперь насчет мобильного клиента. На Android плагин simple-obfs к Shadowsocks называется как Simple Obfuscation. Устанавливаете и задаете в нем такие же параметры, как и в случае конфигов выше. А точнее там настраивается два параметра - метод обфускации и obfs-host.

Чтобы несколько вариантов настроек shadowsocks на сервере работали параллельно, можно например создать под каждый вариант юнит в systemd.

Еще можно сказать пару слов о опции plugin_opts:failover. С помощью этой опции можно замаскировать прокси под www-сервер на этой же машине. Достаточно запустить www-сервер на 127.0.0.1:8443 (TLS) или 127.0.0.1:8080 (HTTP), а в опциях plugin_opts указать failover=127.0.0.1:8443 или failover=127.0.0.1:8080 соответственно. В таком случае обычные HTTP-запросы или TLS-запросы будут выдавать, например, стандартную “заглушку” ненастроенного, только-что-из-коробки, сервера, или какую-либо простую страничку, или вообще редирект на другой адрес. При этом отпадает проблема несоответствия DNS-имени и запрашиваемого хоста.

По теме

Отдельные спасибо

За исправление ошибок в тексте - @n7vsghgkzqel