You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
21 KiB
145 lines
21 KiB
Бэкконнект-сервер
|
|
1 Серверное приложение работает на сервере с операционной системой Windows или Linux (Debian или Ubuntu). Предполагается что будет использоваться модель ASIO (Asynchronious IO) с фиксированным количеством потоков. Единственное обязательное требование: язык с++ (желательно с++11) и сразу 64-битную версию. Нет никаких ограничений по используемым библиотекам.
|
|
Подразумевается, что сервер будет работать как демон на сервере (регистрироваться как сервис в системе не обязательно) и у него будут операции: start, stop, restart.
|
|
2 Серверное приложение туннелирует входящие соединения из серверного порта для SOCKS5 внутрь туннельного соединения, которое создаёт bc-клиент. Входящие соединения в порт для SOCKS5 назовём ClientConnection, соединения создавемые bc-клиентами назовём DataConnection.
|
|
3 Обработка DataConnection. Все DataConnection соединяются к специальному порту, который открывает сервер. Этот порт назовём DataConnPort. В каждом соединении происходит "рукопожатие", в процессе которого bc-клиент проверяет достоверность сервера, а сервер проверяет не продублировалось ли соединение дважды от одного и того же клиента.
|
|
После успешного рукопожатия сервер открывает отдельный TCP-порт для SOCKS5 соединений, закреплённый конкретно за данным DataConnection. Все соединения приходящие на этот порт будут туннелироваться в данный DataConnection.
|
|
3.1 Рукопожатие в DataConnection происходит сразу после TCP-соединения и происходит по инициативе клиента.
|
|
Запрос bc-клиента:
|
|
ProtocolVersion: 4 байта
|
|
RequestSize: 4 байта
|
|
ClientID: 32 байта
|
|
RandHash: 16 байт
|
|
SelfAddressSize: 1 байт
|
|
SelfAddress: 16 байт
|
|
|
|
RequestSize - размер ответа в байтах. В этот размер включаются размер всех полей кроме ProtocolVersion
|
|
ClientID - идентификатор клиент, 32-байтовый блок бинарных данных.
|
|
RandHash - 16 байт случайных данных, для того чтобы сервер продублировал их в ответе
|
|
SelfAddressSize - размер адреса в поле SelfAddres, может быть 4 или 16
|
|
SelfAddress - собственный внешний адрес bc-клиента. Для IPv4 4, для IPv6 16 байт.
|
|
|
|
Ответ сервера:
|
|
ProtocolVersion: 4 байта
|
|
ResponseSize: 4 байта
|
|
ResponseCode: 4 байта
|
|
RandHash: 16 байт
|
|
AesKey: 32 байта
|
|
Padding: 0-16 байт
|
|
|
|
ResponseSize - размер ответа в байтах. В этот размер включаются размер всех полей кроме ProtocolVersion
|
|
ResponseCode - код ответа сервера. Возможнные значения:
|
|
0 - успешно, 1 - сервер перегружен, 2 - внутренняя ошибка на сервере, 3 - не поддерживаемая версия протокола
|
|
RandHash - поле RandHash скопированное из запроса.
|
|
AesKey - не используется в текущей реализации, заполняется случайными байтами.
|
|
Padding - случайные байты в количестве от 0 до 16. В текущей реализации сервер сам выбирает размер этого поля случайным образом и заполняет его случайными байтами.
|
|
Если сервер принимает неправильный запрос рукопожатия, то он сразу же закрывает соединение. Если от одного и того же bc-клиента (bc-клиент идентифицируется по полю ClientID) запрос рукопожатия пришёл в то время, когда у него уже есть активный DataConnection, то предыдущий должен быть закрыт немедленно.
|
|
Едиственный способ получения адреса bc-клиента - это поле SelfAddress в запросе рукопожатия. Получение адреса пира из сокета неправильный способ потому что между bc-клиентом и сервером может стоять DNAT-прокладка.
|
|
|
|
3.2 Протокол взаимодействия. Обмен в обе стороны производится с помощью сообщений следующего формата:
|
|
ConnectionID: 8 байт
|
|
DataSize: 4 байта
|
|
XorKey: 4 байта
|
|
Data: x байт
|
|
ConnectionID - произвольное беззнаковое 64-битное число которое идентифицирует соединение. Сервер и клиент ведут свои списки текущих соединений. Если пришло сообщение с неизвестным ConnectionID, то считается, что создалось новое соединение.
|
|
DataSize - размер данных в поле Data. Если размер данных равен нулю, то соединение считается закрытым.
|
|
XorKey - 4 байтовый ключ для шифрования поля Data.
|
|
Инициировать создание нового соединения может только сервер. Получении сообщений от bc-клиента с неизвестным ConnectionID считается ошибкой bc-клиента, при возникновении данной ситуации DataConnection должно быть разорвано сервером. При получении сообщения с DataSize равным нулю ConnectionID удаляется из внутренней таблицы (как bc-клиента, так и сервера) и ClientConnection ассоциированный с данным ConnectionID должен быть разорван сервером (перед разрывом соединения, сервер должен выслать все данные которые вероятно содержатся во внутреннем буфере сервера и ожидают отправки SOCKS5-клиенту в ClientConnection).
|
|
Выбор значения ConnectionID при инициировании нового соединения, остаётся за разработчиком, оно может быть случайным или последовательным с инкрементом.
|
|
3.2.1 Шифрование поля Data осуществляется следующим образом: поле Data разибвается на части по 4 байта, с каждой частью производится операция xor со значением XorKey. Если последняя часть имеет размер меньший чем 4 байта, то производится побайтовая операция xor c оставшимися байтами данных. Перед каждой операцией xor, значение XorKey модифицируется следующим образом: производится инкремент чётвертого байта XorKey, после чего он перемещается на первое место, а остальные байты сдвигаются (с первого места на второе, со второго на третье, с третье на четвёртое).
|
|
Псевдокод шифрования:
|
|
|
|
BYTE CurrentXorKey[4];
|
|
CopyMemory(CurrentXorKey, XorKey, 4);
|
|
int blocks = DataSize/4;
|
|
for (int i=0; i<blocks ; i++)
|
|
{
|
|
ModifyXorKey(CurrentXorKey);
|
|
for (int j=0; j<DataSize%4; j++)
|
|
{
|
|
Data[i*4 +j] ^= CurrentXorKey[j];
|
|
}
|
|
}
|
|
if (DataSize%4)
|
|
{
|
|
ModifyXorKey(CurrentXorKey);
|
|
for (int j=0; j<DataSize%4; j++)
|
|
{
|
|
Data[blocks*4 +j] ^= CurrentXorKey[j];
|
|
}
|
|
}
|
|
|
|
Функция ModifyXorKey:
|
|
void ModifyXorKey(unsigned char* pp)
|
|
{
|
|
unsigned char temp = pp[3];
|
|
++temp;
|
|
pp[3]=pp[2];
|
|
pp[2]=pp[1];
|
|
pp[1]=pp[0];
|
|
pp[0]=temp;
|
|
}
|
|
|
|
3.3 В случае если у DataConnection произошёл разрыв соединения, необходимо разорвать все ClientConnection связанные с ним. Если по DataConnection нет данных в течении 200 секунд, то его необходимо разорвать.
|
|
В DataConnection раз в 180 секунд bc-клиент отправляет тестовые сообщение с ConnectionID равным 0xFFFFFFFFFFFFFFFF и нулевым DataSize. В ответ на это сообщение сервер должен ответить сообщение с ConnectionID равным 0xFFFFFFFFFFFFFFFE. В противном случае, bc-клиент разорвёт соединение.
|
|
Если DataConnection не после разрыва соединения не пересоздаётся bc-клиентом некоторый промежуток времени (он задаётся в конфиге сервера), то SOCKS5-порт ассоциированный с данным DataConnection должен быть закрыт и освобождён для других DataConnection.
|
|
4 Обработка ClientConnection на сервере должна осуществляться следующим алгоритмом:
|
|
1 Приём соединения
|
|
2 Если ассоциированный с данным портом DataConnection (произошёл дисконнект или что-то другое) не существует, то закрывает соединение.
|
|
3 По протоколу SOCKS5 принимаем запрос аутентификации и отвечаем клиенту что аутентификация не требуется
|
|
4 Все остальные данные туннелируем в DataConnection
|
|
5 Если в какой-то момент DataConnection перестал существовать (произошёл дисконнект или что-то другое), то закрывает соединение.
|
|
|
|
5 У серверного приложения есть конфиг, в котором указано количество рабочих потоков, DataConnPort, время актуальности SOCKS5-порта в секундах после деактуализации DataConnection которое закреплено за ним.
|
|
Подразумевается что все SOCKS5-порты открываются в фиксированном диапазоне портов от 40000 до 65535.
|
|
6 Bc-клиент реализуется в виде исходного кода, который потом будет внедряться в другой проект под ОС Windows. Выбор средств разработки остаётся на разработчиком, но подразумевается, что весь чистые скомпилированные бинарные файлы клиента без вышестоящей логики под обе платформы (x86, x64) не будет превышать 100 КБ. Bc-клиент имеет список адресов серверов куда соединяться, этот список он получает от вышестоящей логики. Список представляет собой пары значений ip:port. Bc-клиент должен перед началом работы их перемешать и начать с ними работать по порядку.
|
|
Если с сервером не удаётся соединиться по причине того, что он перегужен (код ответа рукопожатия - 1), то попытка должна быть повторена через 45 секунд. Если после 5 повторов всё равно получен тот же самый результат, то сервер должен быть сменён ена следующий. Любой другой код ошибки в рукопожатии означает немедленную смену сервера. Если соединение вообще не устанавливается (недоступен адрес или порт, серверное приложение не запущено или просто глючит), то означает немедленную смену сервера. Если bc-клиент дошёл до конца списка серверов, то он должен начать сначала. Если со всеми серверами не получается начать работу, ВСЁ РАВНО продолжаем работу до бесконечности, до тех пор пока вышестоящая логика не сменит список серверов.
|
|
Если в процессе работы с сервером DataConnection по какой-либо причине был разорван, то bc-клиент должен немедленно создать новый.
|
|
6.1 Операции которые bc-клиент предоставляет вышестоящей логике:
|
|
SetServerList() - установка нового списка серверов. Если до этого не было никакого списка, то bc-клиент должен начать работу с ними. Если до этого был какой-то список, то bc-клиент разорвать соедиение с текущий сервером, перезаписать список серверов и начать всё заново.
|
|
RestartCurrentDataConnection() - разорвать текущий DataConnection и начать заново работу с текущим сервером
|
|
ChangeCurrentServer() - разорвать текущий DataConnection и начать заново но уже со следующим сервером в списке
|
|
Stop() -разорвать текущий DataConnection и очистить список серверов.
|
|
6.2 В DataConnection раз в 180 секунд bc-клиент отправляет тестовые сообщение с ConnectionID равным 0xFFFFFFFFFFFFFFFF и нулевым DataSize. В ответ на это сообщение сервер должен ответить сообщение с ConnectionID равным 0xFFFFFFFFFFFFFFFE и нулевым DataSize. В противном случае, bc-клиент должен разорвать соединение.
|
|
6.3 Для получения данных о подключённых клиентах должен быть реализован HTTP-интерейс. Формат запроса
|
|
http://ip:port/<key>/<keypass>/<function>/<param1>/<param2>/
|
|
где:
|
|
key, keypass - данные для аутентификации. Оба параметра могут содержать цифры, латинеские буквы (заглавные и строчные), знаки"_", "-", "+" и символ "точка".
|
|
function - наименование вызова
|
|
param1, param2 - параметр, зависит от значения function
|
|
Все параметры чувствительны к регистру. Все ответы имеют формат text/plain.
|
|
Список пар key, keypass , а также список функций разрешённый для каждого из них должен задаваться в конфиге. Также должен завадаться в конфиге порт для HTTP-интерейса.
|
|
6.3.1 Функция GetAddress - получение SOCKS5-порта для bc-клиента. Параметр - ID в виде HEX-строки. Ответ: строка со значением порта для SOCKS коннектов.
|
|
Пример, запроса:
|
|
http://ip:port/3f45td/F324tH3rty+FDyrfh327gter/GetAddress/AABBCCDDEEFF11223344556677889900/
|
|
Пример ответа: "30127"
|
|
6.3.2 Функция MapPort - проброс локального порта bc-клиента. С использованием функционала программы PortMapper сервер должен отобразить локальный порт целедвого bc-клиента, на порт сервера. Первый параметр - ID в виде HEX-строки, второй параметр целевой локальный порт. Ответ: строка со значением созданного серверного порта.
|
|
Пример, запроса для целевого локального порта 65521:
|
|
http://ip:port/3f45td/F324tH3rty+FDyrfh327gter/GetAddress/AABBCCDDEEFF11223344556677889900/65521/
|
|
Пример ответа: "39017"
|
|
6.3.3 Функция GetList - получение полного списка bc-клиентов. Не принимает параметров. Ответ: список данных о bc-клиентах.
|
|
Формат списка:
|
|
<socks-port>|<ID>|<exit-ip>|<online>\r\n
|
|
socks-port - SOCKS5-порт, число
|
|
ID - идентификатор bc-клиента в виде HEX-строки
|
|
exit-ip - ip-адрес в виде строки
|
|
online - может иметь два значения: "Y" - если DataConnection сейчас активный и работает, "N" - если DataConnection сейчас не работает и порт держится в ожидании пересоединения bc-клиента.
|
|
В списке никаких лишних пробелов, никаких html. Символы "\r\n" есть даже после последней строки.
|
|
|
|
Пример, запроса:
|
|
http://ip:port/3f45td/F324tH3rty+FDyrfh327gter/GetList/
|
|
Пример ответа:
|
|
1123|11223344556677889900AABBCCDDEEFF|11.22.33.44|Y\r\n
|
|
29021|44556677889900AABBCCDDEEFF112233|90.12.34.56|N\r\n
|
|
15696|7889900AABBCCDDEEFF1122334455667|121.125.170.1|Y\r\n
|
|
....
|
|
|
|
Port Mapper
|
|
Приложение реализует проброс удалённого порта на локальный порт сервера.
|
|
Приложение принимает следующую командную строку:
|
|
PortMapper <socks-ip>:<socks-port> <listen-port> <destination-ip>:<destination-port> <idletime>
|
|
Например, PortMapper 11.22.33.44:21345 45674 12.34.45.67:443
|
|
В этом случае, PortMapper открывает для приёма соединений порт 45674 (<listen-port>). При приёме соединения на 45674, он сразу же инициирует соединение с адресом 12.34.45.67:443 (<destination-ip>:<destination-port>) через прокси 11.22.33.44:21345 (<socks-ip>:<socks-port>), после чего передаёт данные от клиента к 12.34.45.67:443 и от 12.34.45.67:443 к клиенту в режиме обычного реле.
|
|
Таким образом, получается что клиент подсоединившийся к порту 45674 на самом деле взаимодействует с 12.34.45.67:443 через прокси. При этом от него скрыт сам адрес 12.34.45.67:443, он видит только серверный порт. При этом адресат 12.34.45.67:443 видит только прокси и не знает про сервер и не знает про клиента присоединившегося к listen-port.
|
|
Соединений на listen-port может быть много.
|
|
Параметр <idletime> задаёт время простоя в секундах, после которого PortMapper должен атоматически завершиться. Простой означает период в секундах, в течении которого не передано ни одного байтах в обе стороны и не принято ни одного соединения. Если передан хотя бы один байт в одну и сторон хотя бы одному из соединений, то это уже не простой.
|