9.4 代理客户端

const server = new net.createServer()
server
  .listen(socks5Port, '0.0.0.0')
  .on('connection', (socket) => {
    socket.setNoDelay()
    udpack.openStream((err, stream) => {
      if (err) {
        logger.error(err)
        socket.destroy()
      } else {
        var proxy = Proxy(socket, stream)
        socket
          .on('data', (buf) => {
            try {
              proxy.handle(buf)
            } catch (err) {
              logger.error('proxy.handle', err)
              socket.destroy()
            }
          })
          .on('close', hadError => {
            proxy.dstTerminate()
          })
      }
    })
  })

UDPack Client 启动 TCP 监听端口,等待 Browser 的连接。有新的连接进来后,会通过之前创建的 UDPack 实例对象 udpack 打开 Stream。并创建 SOCKS 代理对象 Proxy 的实例 proxy,传入 socket 和 stream。

Browser 发送过来的数据通过 proxy.handle(buf) 调用,转交给 proxy 处理,而 proxy 内部会通过 stream 发送到 UDPack Server 端。同样的,UDPack Server 端也会通过相同的 stream 对象发送数据,UDPack Client 端接收到数据后,通过 socket 对象转发给 Browser。

dstConnect(req) {
  let { domain, ip, port } = req

  let authority = Authority.create({
    domain,
    ip,
    port
  })

  let packet = Packet.create({
    sid: this._session.id,
    cmd: 'connect',
    authority
  })

  let buffer = Packet.encode(packet).finish()

  stream.write(buffer, (err) => {
    if (err) {
      logger.error(err.message, err.stack)
    }
  })
  logger.debug('dstConnect', `sid [${this._session.id}]`, req)
}

上方是 UDPack Client 请求 UDPack Server 打开一个到 authority 的连接通道的代码。首先构造 Packet 对象,然后通过 protobuf 序列化为字节数组,然后通过 stream 发送出去。

stream.on('data', (buffer) => {
  let packet = Packet.decode(buffer)
  if (packet.cmd === 'connect') {
    if (packet.connectRes.succeeded) {
      socket._proxy.replyDstConnect(packet)
    }
  } else if (packet.cmd === 'data') {
    socket._proxy.replyData(packet)
  } else if (packet.cmd === 'terminate') {
    socket._proxy.terminate(packet)
  }
})

如上述代码所示,UDPack Server 发送过来的数据,经过 protobuf 解码得到 Packet 对象,然后根据 cmd 命令字执行不同的逻辑。如 packet.cmd === 'data' 时,会通过调用 socket._proxy.replyData(packet) 转发数据回 Browser。