Источник этой статьиshenyifengtk.github.io/Если перепечатано, укажите источник
Представляем носки5
socks5s — это сетевой протокол передачи, который в основном используется для промежуточной передачи данных между клиентом и внешним сетевым сервером. Когда клиент за брандмауэром хочет получить доступ к внешнему серверу, он подключается к прокси-серверу SOCKS. Этот прокси-сервер контролирует право клиента на доступ к внешней сети.Если это разрешено, он отправляет запрос клиента на внешний сервер.
Согласно модели OSI, SOCKS — это протокол сеансового уровня, расположенный между уровнем представления и транспортным уровнем, то есть socks — это протокол поверх TCP.по сравнению с HTTP-прокси
Прокси-серверы HTTP могут проксировать только HTTP-запросы.Такие протоколы, как TCP и HTTPS, очень слабы и имеют определенные ограничения. SOCKS работает на более низком уровне, чем HTTP-прокси: SOCKS использует протокол рукопожатия для информирования прокси-программы о соединениях, которые его клиенты пытаются установить SOCKS, а затем делает это настолько прозрачно, насколько это возможно, тогда как обычные прокси-серверы могут интерпретировать и > перезаписывать заголовки (например, , Используйте другой базовый протокол, например FTP, однако прокси-серверы HTTP просто пересылают HTTP-запросы на нужный HTTP-сервер). Хотя прокси-серверы HTTP имеют разные шаблоны использования, метод CONNECT позволяет Допускается переадресация TCP-соединений, однако прокси-серверы SOCKS также могут пересылать трафик UDP и обратные прокси-серверы, тогда как прокси-серверы HTTP не могут. Прокси-серверы HTTP обычно лучше знают протокол HTTP и выполняют фильтрацию более высокого уровня (хотя обычно используются только для GET и метод POST, а не для метода CONNECT).
Выберите метод аутентификации
Вообще говоря, процесс соединения socks, сначала клиент отправляет пакет данных прокси-серверу socks
Var | NMETHODS | METHODS |
---|---|---|
1 | 1 | 0-255 |
Единицы в таблице представляют количество цифр
- VarУказывает версию SOCK, должно быть 5;
- NMETHODSвыражатьMETHODSдлина секции
-
METHODSУказывает список методов аутентификации, поддерживаемых клиентом, каждый метод занимает 1 байт. Текущее определение
- 0x00 Аутентификация не требуется
- 0x01 GSSAPI
- 0x02 Аутентификация по имени пользователя и паролю
- 0x03 — 0x7F, выделенный IANA (зарезервировано)
- 0x80 - 0xFE зарезервировано для закрытых методов
- 0xFF Нет приемлемого метода
Сервер ответит клиенту
VER | METHOD |
---|---|
1 | 1 |
- VarУказывает версию SOCK, должно быть 5;
- METHODЯвляется ли метод выбора на стороне сервера, значение этого вышеMETHODSодин из списка. Если клиент поддерживает 0x00, 0x01, 0x02, эти три метода. Сервер выберет только один метод аутентификации и вернет его клиенту.Если он вернет 0xFF, это означает, что метод аутентификации не выбран, и клиенту необходимо закрыть соединение. Сначала мы используем простой Nodejs для реализации рукопожатия sock-соединения.Просмотрите дейтаграмму, отправленную клиентом.
const net = require('net');
let server = net.createServer(sock =>{
sock.once('data', (data)=>{
console.log(data);
});
});
server.listen(8888,'localhost');
Используйте инструмент curl для подключения nodejs
curl -x socks5://localhost:8888 https://www.baidu.com
консольный вывод
<Buffer 05 02 00 01>
Использовать аутентификацию по паролю учетной записи
Когда сервер выбирает метод аутентификации учетной записи и пароля 0x02, клиент начинает отправлять номер учетной записи и пароль Формат пакета данных следующий: (в байтах)
VER | ULEN | UNAME | PLEN | PASSWD |
---|---|---|---|---|
1 | 1 | 1 to 255 | 1 | 1 to 255 |
- VER — это версия SOCKS.
- Длина имени пользователя ULEN
- Строка учетной записи UNAME
- Длина пароля PLEN
- Строка пароля PASSWD
Видно, что пароль учетной записипередача открытого текста, очень небезопасно. После завершения проверки на стороне сервера он ответит следующими данными():
VER | STATUS |
---|---|
1 | 1 |
- СТАТУС 0x00 означает успех, 0x01 означает отказ
запрос пакета
После завершения аутентификации клиент может отправить информацию запроса. Клиент начинает инкапсулировать информацию запроса Формат запроса SOCKS5 (в байтах):
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | динамичный | 2 |
- VER — это версия SOCKS, здесь должно быть 0x05;
- CMD — это код команды для SOCK
-
0x01 означает запрос CONNECT
- Запрос CONNECT может открыть двусторонний канал связи между клиентом и запрошенным ресурсом. Его можно использовать для создания туннелей. Например,**
CONNECT
** Может использоваться для доступа к принятымSSL (HTTPS) сайт протокола. Клиент просит прокси-сервер туннелировать TCP-соединение с хостом назначения. После этого сервер установит соединение с хостом назначения вместо клиента. После установления соединения прокси-сервер отправляет или получает поток TCP-сообщений клиенту.
- Запрос CONNECT может открыть двусторонний канал связи между клиентом и запрошенным ресурсом. Его можно использовать для создания туннелей. Например,**
-
0x02 означает запрос BIND
Метод Bind используется, когда целевому хосту необходимо активно подключаться к клиенту (протокол ftp).
Когда CMD в пакете данных, полученном сервером, равен X'02', сервер использует метод Bind для прокси. При использовании метода Bind в качестве прокси сервер должен отвечать клиенту не более двух раз на пакеты данных.
Сервер использует TCP-протокол для подключения к соответствующему (DST.ADDR, DST.PORT), и в случае сбоя возвращает неудачный пакет и закрывает сеанс. В случае успеха прослушайте (BND.ADDR, BND.PORT), чтобы принять запрос запрашивающего хоста, а затем верните первый пакет данных, который используется клиентом для отправки данных с указанием адреса клиента подключения целевого хоста и сумки порта.
После того, как целевому узлу удастся или не удастся подключиться к адресу и порту, указанным сервером, ответьте на второй пакет. В это время (BND.ADDR, BND.PORT) должны быть адрес и порт соединения между целевым хостом и сервером.
-
0x03 означает переадресацию UDP
-
- RSV 0x00, зарезервировано
- тип АТИП
- 0x01 IPv4-адрес, часть DST.ADDR длиной 4 байта
- 0x03 Доменное имя, первый байт части DST.ADDR — это длина доменного имени, а остальная часть DST.ADDR — доменное имя без конца \0.
- 0x04 IPv6-адрес длиной 16 байт.
- DST.ADDR адрес назначения
- Порт назначения DST.PORT в сетевом порядке байтов образец данных
<Buffer 05 01 00 01 0e d7 b1 26 01 bb>
Сервер инкапсулирует данные в соответствии с клиентом, запрашивает удаленный сервер и отвечает клиенту в следующем фиксированном формате.
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | динамичный | 2 |
- VER — это версия SOCKS, здесь должно быть 0x05;
- Поле ответа REP
- 0x00 означает успех
- 0x01 Нормальное соединение с сервером SOCKS не удалось
- 0x02 Соединение не разрешено существующими правилами
- 0x03 сеть недоступна
- 0x04 хост недоступен
- 0x05 В соединении отказано
- 0x06 Тайм-аут TTL
- 0x07 Неподдерживаемая команда
- 0x08 Неподдерживаемый тип адреса
- 0x09 - 0xFF не определено
- RSV 0x00, зарезервировано
- ATYP
- 0x01 IPv4-адрес, часть DST.ADDR длиной 4 байта
- 0x03 Доменное имя, первый байт части DST.ADDR — это длина доменного имени, остальная часть DST.ADDR — доменное имя, и в конце нет \0.
- 0x04 IPv6-адрес длиной 16 байт.
- Адрес привязки сервера BND.ADDR
- BND.PORT Порт, к которому привязан сервер, в сетевом порядке байтов.
Используйте nodejs для реализации запроса CONNECT
const net = require('net');
const dns = require('dns');
const AUTHMETHODS = { //只支持这两种方法认证
NOAUTH: 0,
USERPASS: 2
}
//创建socks5监听
let socket = net.createServer(sock => {
//监听错误
sock.on('error', (err) => {
console.error('error code %s',err.code);
console.error(err);
});
sock.on('close', () => {
sock.destroyed || sock.destroy();
});
sock.once('data', autherHandler.bind(sock)); //处理认证方式
});
let autherHandler = function (data) {
let sock = this;
console.log('autherHandler ', data);
const VERSION = parseInt(data[0], 10);
if (VERSION != 5) { //不支持其他版本socks协议
sock.destoryed || sock.destory();
return false;
}
const methodBuf = data.slice(2); //方法列表
let methods = [];
for (let i = 0; i < methodBuf.length; i++)
methods.push(methodBuf[i]);
//先判断账号密码方式
let kind = methods.find(method => method === AUTHMETHODS.USERPASS);
if (kind) {
let buf = Buffer.from([VERSION, AUTHMETHODS.USERPASS]);
sock.write(buf);
sock.once('data', passwdHandler.bind(sock));
} else {
kind = methods.find(method => method === AUTHMETHODS.NOAUTH);
if (kind === 0) {
let buf = Buffer.from([VERSION, AUTHMETHODS.NOAUTH]);
sock.write(buf);
sock.once('data', requestHandler.bind(sock));
} else {
let buf = Buffer.from([VERSION, 0xff]);
sock.write(buf);
return false;
}
}
}
/**
* 认证账号密码
*/
let passwdHandler = function (data) {
let sock = this;
console.log('data ', data);
let ulen = parseInt(data[1], 10);
let username = data.slice(2, 2 + ulen).toString('utf8');
let password = data.slice(3 + ulen).toString('utf8');
if (username === 'admin' && password === '123456') {
sock.write(Buffer.from([5, 0]));
} else {
sock.write(Buffer.from([5, 1]));
return false;
}
sock.once('data', requestHandler.bind(sock));
}
/**
* 处理客户端请求
*/
let requestHandler = function (data) {
let sock = this;
const VERSION = data[0];
let cmd = data[1]; // 0x01 先支持 CONNECT连接
if(cmd !== 1)
console.error('不支持其他连接 %d',cmd);
let flag = VERSION === 5 && cmd < 4 && data[2] === 0;
if (! flag)
return false;
let atyp = data[3];
let host,
port = data.slice(data.length - 2).readInt16BE(0);
let copyBuf = Buffer.allocUnsafe(data.length);
data.copy(copyBuf);
if (atyp === 1) { //使用ip 连接
host = hostname(data.slice(4, 8));
//开始连接主机!
connect(host, port, copyBuf, sock);
} else if (atyp === 3) { //使用域名
let len = parseInt(data[4], 10);
host = data.slice(5, 5 + len).toString('utf8');
if (!domainVerify(host)){
console.log('domain is fialure %s ', host);
return false;
}
console.log('host %s', host);
dns.lookup(host, (err, ip, version) => {
if(err){
console.log(err)
return;
}
connect(ip, port, copyBuf, sock);
});
}
}
let connect = function (host, port, data, sock) {
if(port < 0 || host === '127.0.0.1')
return;
console.log('host %s port %d', host, port);
let socket = new net.Socket();
socket.connect(port, host, () => {
data[1] = 0x00;
if(sock.writable){
sock.write(data);
sock.pipe(socket);
socket.pipe(sock);
}
});
socket.on('close', () => {
socket.destroyed || socket.destroy();
});
socket.on('error', err => {
if (err) {
console.error('connect %s:%d err',host,port);
data[1] = 0x03;
if(sock.writable)
sock.end(data);
console.error(err);
socket.end();
}
})
}
let hostname = function (buf) {
let hostName = '';
if (buf.length === 4) {
for (let i = 0; i < buf.length; i++) {
hostName += parseInt(buf[i], 10);
if (i !== 3)
hostName += '.';
}
} else if (buf.length == 16) {
for (let i = 0; i < 16; i += 2) {
let part = buf.slice(i, i + 2).readUInt16BE(0).toString(16);
hostName += part;
if (i != 14)
hostName += ':';
}
}
return hostName;
}
/**
* 校验域名是否合法
*/
let domainVerify = function (host) {
let regex = new RegExp(/^([a-zA-Z0-9|\-|_]+\.)?[a-zA-Z0-9|\-|_]+\.[a-zA-Z0-9|\-|_]+(\.[a-zA-Z0-9|\-|_]+)*$/);
return regex.test(host);
}
socket.listen(8888,() => console.log('socks5 proxy running ...')).on('error', err => console.error(err));
end
При использовании в связке с браузером я обнаружил, что нет возможности загрузить видео Douyu.Почему-то у Youku нет проблем.
Я только что узнал некоторые знания о NodeJs, и письмо так себе.Если есть что-то, что не очень хорошо написано, пожалуйста, укажите это и обсудите это вместе. Когда я впервые посмотрел на протокол, я подумал, что после того, как клиент (браузер) и сервер завершат запрос аутентификации, обе стороны будут поддерживать длительное TCP-соединение, и клиент напрямую отправит пакет запроса инкапсуляции. Фактически каждый запрос клиента начинается с аутентификации, и каждый запрос не зависит друг от друга, поэтомуonce
Этот метод особенно подходит здесь