Dart实战——Socks5服务器

2020-10-29 10:14:24 浏览数 (1)

视频教程

视频教程已上传B站,本篇将视频教程中的资料和代码进行了整理,可通过以下链接观看,注意结合本文档食用才更配哦

B站链接:https://www.bilibili.com/video/BV1954y1k7nc/

SOCKS5协议

百度百科

SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问Internet网中的服务器,或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器, 模拟了一个前端的行为。在这里,前端和SOCKS5之间也是通过TCP/IP协议进行通讯,前端将原本要发送给真正服务器的请求发送给SOCKS5服务器,然后SOCKS5服务器将请求转发给真正的服务器。

维基百科

SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。SOCKS是"SOCKetS"的缩写。

当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。

这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到SOCKS4。最新协议是SOCKS5,与前一版本相比,增加支持UDP、验证,以及IPv6。

根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。

SOCKS协议不提供加密。

SOCKS5比SOCKS4a多了验证、IPv6、UDP支持。创建与SOCKS5服务器的TCP连接后客户端需要先发送请求来确认协议版本及认证方式,格式为(以字节为单位):

VER

NMETHODS

METHODS

1

1

1-255

  • VER是SOCKS版本,这里应该是0x05;
  • NMETHODS是METHODS部分的长度;
  • METHODS是客户端支持的认证方式列表,每个方法占1字节。当前的定义是:
    • 0x00 不需要认证
    • 0x01 GSSAPI[1]
    • 0x02 用户名、密码认证
    • 0x03 - 0x7F由IANA[2]分配(保留)
    • 0x80 - 0xFE为私人方法保留
    • 0xFF 无可接受的方法

服务器从客户端提供的方法中选择一个并通过以下消息通知客户端(以字节为单位):

VER

METHOD

1

1

  • VER是SOCKS版本,这里应该是0x05;
  • METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,客户端需要关闭连接。

之后客户端和服务端根据选定的认证方式执行对应的认证。

认证结束后客户端就可以发送请求信息。如果认证方法有特殊封装要求,请求必须按照方法所定义的方式进行封装。

SOCKS5请求格式(以字节为单位):

VER

CMD

RSV

ATYP

DST.ADDR

DST.PORT

1

1

0x00

1

动态

2

  • VER是SOCKS版本,这里应该是0x05;
  • CMD是SOCK的命令码
    • 0x01表示CONNECT请求
    • 0x02表示BIND请求
    • 0x03表示UDP转发
  • RSV 0x00,保留
  • ATYP DST.ADDR类型
    • 0x01 IPv4地址,DST.ADDR部分4字节长度
    • 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有结尾。
    • 0x04 IPv6地址,16个字节长度。
  • DST.ADDR 目的地址
  • DST.PORT 网络字节序表示的目的端口

服务器按以下格式回应客户端的请求(以字节为单位):

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 BND.ADDR类型
    • 0x01 IPv4地址,DST.ADDR部分4字节长度
    • 0x03域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有结尾。
    • 0x04 IPv6地址,16个字节长度。
  • BND.ADDR 服务器绑定的地址
  • BND.PORT 网络字节序表示的服务器绑定的端口

示意图

Dart实现源码

这里我们使用Dart语言来实现简单的SOCKS5服务器。

需要注意,这里使用的Dart SDK 版本为2.10,请尽量升级你本地Dart到最新版本,因为这里使用到的RawSocket相关的某些API 是SDK 2.8之后的版本才提供的。

视频课程中是在Windows10系统上进行的本地测试,如果有远程主机,可在部署在远程主机进行测试

main.dart

代码语言:javascript复制
import 'dart:io';

import '../lib/socks5.dart';

const server_port = 8082;

void main() {
  print('socks5 service run on $server_port');
  startSocks5Server();
}

void startSocks5Server() async {
  RawServerSocket serverSocket =
      await RawServerSocket.bind(InternetAddress.anyIPv4, server_port);

  // 等待客户端socket连接
  await for (RawSocket socket in serverSocket) {
    Socks5Helper(socket);
  }
  serverSocket.close();
}

socks5.dart

代码语言:javascript复制
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'dart:typed_data';

class Socks5Helper {
  RawSocket clientSocket;
  RawSocket targetSocket;

  // 是否完成了握手
  bool handshakeCompleted = false;
  // 是否建立了连接
  bool connCompleted = false;

  Socks5Helper(this.clientSocket) {
    // 监听Socks客户端的数据
    clientSocket.listen(
        (event) async {
          if (event == RawSocketEvent.read) {
            if (!handshakeCompleted) {
              // 没有完成握手
              handshakeCompleted = handshake();
            } else if (!connCompleted) {
              // 没有建立连接通道
              connCompleted = await createConnection();
            } else {
              if (targetSocket != null) {
                // 转发客户端的数据
                forward(clientSocket, targetSocket);
              }
            }
          }
        },
        onError: (e) => print("Socks5Helper onError:$e"),
        onDone: () {
          // 连接断开回调
          clientSocket?.close();
          targetSocket?.close();
        });
  }

  //1. socks5握手
  bool handshake() {
    // 从客户端socket中读取字节数据
    Uint8List bytes = clientSocket.read(clientSocket.available());
    ByteData bd = ByteData.sublistView(bytes);
    var req = HandShakeRequest.from(bd);

    if (req.verify()) {
      clientSocket.write(HandShakeResponse.from(0x00).toByte());
      return true;
    }
    return false;
  }

  //2. 建立连接
  Future<bool> createConnection() async {
    // 从客户端socket中读取字节数据
    Uint8List bytes = clientSocket.read(clientSocket.available());
    ByteData bd = ByteData.sublistView(bytes);

    var req = Socks5Request.from(bd);
    // 仅支持CONNECT方式
    if (req.cmd != 0x01) {
      print("only support CONNECT CMD=${req.cmd}");
      var res = Socks5Response.from(
          0x07, IPv4(InternetAddress.tryParse('127.0.0.1').rawAddress, 8082));
      clientSocket.write(res.toByte());
      clientSocket.close();
      return false;
    }

    try {
      // 获取目标服务器IP地址
      var addr = await req.getAddress();
      // 访问目标服务器,建立Sockets连接
      targetSocket = await RawSocket.connect(addr, req.dst.port,
          timeout: Duration(seconds: 15));

      print('${addr.host} => ${addr.address}');

      var res = Socks5Response.from(
          0x00, IPv4(targetSocket.address.rawAddress, targetSocket.port));

      clientSocket.write(res.toByte());

      // 第二阶段完成,设置目标服务器的监听
      targetSocket.listen(
          (event) {
            if (event == RawSocketEvent.read) {
              // 数据转发
              forward(targetSocket, clientSocket);
            }
          },
          onError: (e) => print("target socket error:$e"),
          onDone: () {
            clientSocket.close();
            targetSocket.close();
          });
      return true;
    } catch (e) {
      var res = Socks5Response.from(
          0x06, IPv4(InternetAddress.tryParse("127.0.0.1").rawAddress, 8082));
      clientSocket.write(res.toByte());
      clientSocket.close();

      var errorMsg = (await req.getAddress()).host   " => $e";
      print(errorMsg);
    }
    return false;
  }

  //3. 转发请求
  void forward(RawSocket input, RawSocket output) {
    // 从输入源读取
    var bytes = input.read(input.available());
    // 发送给目标
    output.write(bytes);
  }
}


/// 握手请求
class HandShakeRequest {
  int ver;
  int nmethods;
  Uint8List methods;

  HandShakeRequest.from(ByteData blob) {
    ver = blob.getUint8(0);
    nmethods = blob.getUint8(1);
    if (nmethods > 0) {
      methods = blob.buffer.asUint8List(2, nmethods);
    }
  }

  // 验证(暂未实现,空架子)
  bool verify() {
    if (ver != 0x05) {
      print("version is not supported");
      return false;
    }

    for (var it in methods) {
      if (it == 0x00) {
        return true;
      }
    }

    print("verification method is not supported");
    return false;
  }
}

/// 握手响应
class HandShakeResponse {
  int ver = 0x05;
  int method;

  HandShakeResponse.from(this.method);

  Uint8List toByte() {
    ByteData bd = ByteData(2);
    bd.setUint8(0, ver);
    bd.setUint8(1, method);
    return bd.buffer.asUint8List();
  }
}

/// 协商请求
class Socks5Request {
  int ver;
  int cmd;
  int rsv;
  int atyp;

  Dst dst;

  Socks5Request.from(ByteData blob) {
    ver = blob.getUint8(0);
    cmd = blob.getUint8(1);
    rsv = blob.getUint8(2);
    atyp = blob.getUint8(3);

    switch (atyp) {
      case 0x01: // IPv4地址
        dst = IPv4(blob.buffer.asUint8List(4, 4), blob.getUint16(8));
        break;
      case 0x03: // 域名地址
        var len = blob.getUint8(4);
        var hostBytes = blob.buffer.asUint8List(5, len);
        dst = Domain(ascii.decode(hostBytes), blob.getUint16(len   5));
        break;
      case 0x04:
        break;
    }
  }

  Future<InternetAddress> getAddress() async {
    switch (atyp) {
      case 0x01:
        return (dst as IPv4).address;
      case 0x03:
        List<InternetAddress> addressList =
            await InternetAddress.lookup((dst as Domain).host);
        return addressList.first;
      default:
        return null;
    }
  }
}

class Dst {
  int port;
}

class IPv4 implements Dst, Bnd {
  int port;
  InternetAddress address;

  IPv4(Uint8List addr, this.port) {
    address =
        InternetAddress.fromRawAddress(addr, type: InternetAddressType.IPv4);
  }
}

class IPv6 implements Dst, Bnd {
  int port;
}

class Domain implements Dst, Bnd {
  int port;
  String host;

  Domain(this.host, this.port);
}

class Bnd {
  int port;
}

/// 协商响应
class Socks5Response {
  int ver = 0x05;
  int rep;
  int rsv = 0x00;
  int atyp = 0x01;

  Bnd bnd;

  Socks5Response.from(this.rep, this.bnd);

  Uint8List toByte() {
    var bd = ByteData(10);
    bd.setUint8(0, ver);
    bd.setUint8(1, rep);
    bd.setUint8(2, rsv);
    bd.setUint8(3, atyp);

    Uint8List rawAddr = (bnd as IPv4).address.rawAddress;
    for (var i = 0; i < 4; i  ) {
      bd.setUint8(4   i, rawAddr[i]);
    }
    bd.setUint16(8, bnd.port);

    return bd.buffer.asUint8List();
  }
}

课程制作不易,需要您的鼓励支持,请点个赞再走哦!

参考资料

[1]

GSSAPI: /w/index.php?title=GSSAPI&action=edit&redlink=1

[2]

IANA: /wiki/IANA

0 人点赞