Shenyifengtk.github. IO /
Socks5 is introduced
Socks5s is a network transmission protocol used for communication between clients and extranet servers. When a client behind the firewall accesses an external server, it connects to the SOCKS proxy server. This proxy server controls the client’s access to the Internet and, if allowed, sends requests to an external server.
Compare that to HTTP proxies
HTTP proxies can only represent HTTP requests. Protocols such as TCP and HTTPS are weak and have certain limitations. SOCKS works at a lower level than HTTP proxies: SOCKS uses a handshake protocol to notify the proxy software of the connection SOCKS that its clients are attempting to make, and then operates as transparently as possible, while regular proxies may interpret and > override headers (for example, using another underlying protocol, such as FTP; However, the HTTP proxy simply forwards HTTP requests to the desired HTTP server. Although HTTP proxies have different usage patterns, the CONNECT method allows forwarding TCP connections; However, a SOCKS proxy can also forward UDP traffic and reverse proxies, while an HTTP proxy cannot. HTTP proxies are generally more knowledgeable about the HTTP protocol and perform a higher level of filtering (although typically only used for GET and POST methods, not CONNECT methods).
Official protocol RFC
Select an authentication method
The socks connection process is described in general. First, the client sends a data packet to the SOCKS proxy
Var | NMETHODS | METHODS |
---|---|---|
1 | 1 | 0-255. |
The units in the table represent bits
- Var indicates SOCK version, which should be 5.
- NMETHODS indicates the length of the METHODS section
- METHODSIndicates the list of authentication methods supported by the client. Each method takes 1 byte. The current definition is
- 0x00 No authentication is required
- 0x01 GSSAPI
- 0x02 User Name and Password Authentication
- 0x03-0x7F Allocated by IANA (Reserved)
- 0x80-0xfe is reserved for private methods
- 0xFF Unacceptable method
The server responds to the client
VER | METHOD |
---|---|
1 | 1 |
- Var indicates SOCK version, which should be 5.
- METHOD is the METHOD selected by the server. This value is one of the METHODS listed above. If the client supports 0x00, 0x01, 0x02, these three methods. Only one authentication method is selected and returned to the client. If 0xFF is returned, no authentication method is selected and the client needs to close the connection. Let’s start with a simple Nodejs handshake in sock connection. View client sending datagrams
const net = require('net');
let server = net.createServer(sock= >{
sock.once('data', (data)=>{
console.log(data);
});
});
server.listen(8888.'localhost');
Copy the code
Use the curl tool to connect nodeJS
curl -x socks5://localhost:8888 https://www.baidu.com
Copy the code
The console output
<Buffer 05 02 00 01>
Use the account and password for authentication
After the server selects account and password authentication mode 0x02, the client sends the account and password in the following format: (in bytes)
VER | ULEN | UNAME | PLEN | PASSWD |
---|---|---|---|---|
1 | 1 | 1 to 255 | 1 | 1 to 255 |
- VER is the SOCKS version
- ULEN Indicates the length of the user name
- UNAME account string
- PLEN Password length
- PASSWD password string
It can be seen that the account and password are transmitted in plain text, which is very insecure. After the verification on the server is complete, the following data will be responded ():
VER | STATUS |
---|---|
1 | 1 |
- STATUS 0x00 indicates success and 0x01 indicates failure
Encapsulates the request
After the authentication is complete, the client can send request information. The client starts to encapsulate the request information SOCKS5 request format (in bytes) :
VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | dynamic | 2 |
- VER is the SOCKS version, which should be 0x05;
- CMD is the SOCK command code
-
0x01 indicates a CONNECT request
- CONNECT requests can open a two-way communication channel between a client and the requested resource. It can be used to create tunnels. For example, * *
CONNECT
** can be used to access adoption nowSSL (HTTPS) protocol site. The client requires the proxy server to use the TCP connection as a tunnel to the destination host. Then the server replaces the client to establish a connection with the destination host. Once the connection is established, the proxy server sends or receives TCP message flows to and from the client.
- CONNECT requests can open a two-way communication channel between a client and the requested resource. It can be used to create tunnels. For example, * *
-
0x02 indicates a BIND request
The Bind method is used when the target host needs to actively connect to the client (FTP)
When the server receives a packet with CMD X’02’, the server uses the Bind method for proxy. The Bind proxy requires the server to reply to the client at most twice.
The server uses THE TCP protocol to connect to the corresponding (dst.addr, dst.port). If the connection fails, the server returns a failed packet and closes the session. If successful, it listens (bnd.addr, BNd.port) to accept the request from the requesting host and then returns the first packet that the client uses to send a packet specifying the destination host’s address and PORT to connect to the client.
A second packet is returned after the destination host successfully or fails to connect to the address and port specified by the server. In this case, (bnd.addr, bnd.port) should be the address and PORT of the connection established between the destination host and the server.
-
0x03 indicates UDP forwarding
-
- RSV 0x00, reserved
- ATYP type
- 0x01 IPv4 address, dst.addr part is 4 bytes long
- 0x03 Domain name, the first byte of dst.addr is the length of the domain name, and the rest of dst.addr is the domain name without the \0 ending.
- 0x04 IPv6 address, 16 bytes long.
- Dst.addr Indicates the destination address
- Dst. PORT Destination PORT example data in network byte order
<Buffer 05 01 00 01 0e d7 b1 26 01 bb>
The server encapsulates the data according to the client and requests the remote server to respond to the client in the following fixed format.
VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
---|---|---|---|---|---|
1 | 1 | 0x00 | 1 | dynamic | 2 |
- VER is the SOCKS version, which should be 0x05;
- REP response field
- 0x00 indicates success
- 0x01 Common SOCKS Server Connection failed
- 0x02 Connection is not allowed by existing rules
- 0x03 Network Unreachable
- 0x04 Host Unreachable
- 0x05 Connection rejected
- 0 x06 TTL timeout
- 0x07 Unsupported command
- 0x08 Unsupported address type
- 0x09-0xFF is not defined
- RSV 0x00, reserved
- ATYP
- 0x01 IPv4 address, dst.addr part is 4 bytes long
- 0x03 Domain name, the first byte of dst.addr is the length of the domain name, and the rest of dst.addr is the domain name without the \0 ending.
- 0x04 IPv6 address, 16 bytes long.
- Bnd. ADDR Specifies the IP address bound to the server
- Bnd. PORT Indicates the server bound PORT in network byte order
CONNECT requests are implemented using NodeJS
const net = require('net');
const dns = require('dns');
const AUTHMETHODS = { // Only the two authentication methods are supported
NOAUTH: 0.USERPASS: 2
}
// Create a socks5 listener
let socket = net.createServer(sock= > {
// Listening error
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)); // Handle authentication mode
});
let autherHandler = function (data) {
let sock = this;
console.log('autherHandler ', data);
const VERSION = parseInt(data[0].10);
if(VERSION ! =5) { // Other versions of socks are not supported
sock.destoryed || sock.destory();
return false;
}
const methodBuf = data.slice(2); // List of methods
let methods = [];
for (let i = 0; i < methodBuf.length; i++)
methods.push(methodBuf[i]);
// Determine the password
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; }}}/** * Authentication account password */
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));
}
/** ** handle client requests */
let requestHandler = function (data) {
let sock = this;
const VERSION = data[0];
let cmd = data[1]; // 0x01 supports CONNECT connections first
if(cmd ! = =1)
console.error('No other connections %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) { // Use IP connection
host = hostname(data.slice(4.8));
// Start connecting to the host!
connect(host, port, copyBuf, sock);
} else if (atyp === 3) { // Use the domain name
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;
}
/** * Verify that the domain name is valid */
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));
Copy the code
end
And browser combined use, found no way to load douyu video, I do not know what principle, youku have no problem. NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs NodeJs At the beginning of the protocol, I thought that the client (browser) and the server would maintain a TCP long connection after the authentication request, and the client directly sent the encapsulated request packet. In fact, each client request starts with authentication, and each request is independent of each other, so the once method is particularly suitable here