socks 专栏 —— 6. 初版代码实现

·  阅读 40

6. 初版代码实现

话不多说,直接上完整代码+注释:

const net = require('net');
​
const port = 4000;
​
const STATE = {
  INIT: 0,
  SENT_FIRST_HANDSHAKE_RESPONSE: 1,
​
  ERR: -1,
};
​
const server = net.createServer((socket) => {
  let state = STATE.INIT;
​
  const closeSocket = () => {
    socket.destroy();
    state = STATE.ERR;
  };
​
  socket.on('data', (data) => {
    switch (state) {
      case STATE.INIT: // 初始状态,此时收到的 data 为 client 发过来的握手数据
        const version = data.readUInt8(0); // 第 0 个字节为 version
        if (version !== 5) {
          closeSocket();
          return;
        }
​
        socket.write(Buffer.from([0x05, 0x00])); // 返回 0x0500,表示不需要认证
        state = STATE.SENT_FIRST_HANDSHAKE_RESPONSE;
        break;
​
      case STATE.SENT_FIRST_HANDSHAKE_RESPONSE: // 此时收到的 data 为 client 发过来的包含 CMD,目标地址等信息的连接请求
        const cmd = data.readUInt8(1);
​
        if (cmd !== 1) {
          // 不为 CONNECT
​
          Buffer.from([
            0x05, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
          ]); // 发送给 client,不支持当前的 CMD,连接失败
​
          closeSocket();
          return;
        }
​
        // 读取 target server 的地址和端口
​
        let addrType = data.readUInt8(3);
        let addrLength, remoteAddr, remotePort;
​
        if (addrType === 4) {
          // 不支持 ipv6
          socket.write(
            Buffer.from([
              0x05, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            ])
          );
          closeSocket();
          return;
        }
​
        if (addrType === 3) {
          // domain
          addrLength = data.readUInt8(4);
          remoteAddr = data.slice(5, 5 + addrLength).toString();
          remotePort = data.readUInt16BE(data.length - 2);
        } else if (addrType === 1) {
          // ipv4
          remoteAddr = data.slice(4, 8).join('.');
          remotePort = data.readUInt16BE(data.length - 2);
        }
​
        // 连接 target server
        const remote = net.createConnection(
          { host: remoteAddr, port: remotePort },
          () => {
            socket.write(
              Buffer.from([
                0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
              ])
            ); // 发送给 client,表示连接成功了
​
            state = STATE.SENT_CONNECT_RESPONSE;
​
            console.log(`connecting ${remoteAddr}:${remotePort}`);
            // 最关键的两行代码:将两个 socket pipe 起来,从而任意一方读取的数据会写入到另一方,实现转发过程
            socket.pipe(remote);
            remote.pipe(socket);
          }
        );
​
        remote.on('error', (err) => {
          remote.destroy();
          closeSocket();
        });
​
        break;
    }
  });
​
  socket.on('error', (err) => {
    console.log('socket error', err.message);
  });
});
​
server.listen(port, () => {
  console.log(`socks5 proxy server is listening on port ${port}`);
});
​
复制代码

将第 5 章提到的 socks5 交互过程翻译成代码就行了。其中最核心的两行就是

socket.pipe(remote);
remote.pipe(socket);
复制代码

约等于

socket.on('data', data => remote.write(data));
remote.on('data', data => socket.write(data));
复制代码

也是转发数据的关键部分。

和 http server 不同的是,tcp server 数据交互都是 Buffer,所以从 data 中获取数据,就需要用到 Buffer instanceread 相关方法。

将该服务器在本地运行,然后将代理设置为socks5 + 127.0.0.1:4000。可以在系统中设置,也可以使用一些浏览器代理插件(比如 SwitchyOmega )。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改