前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dart实战——Socks5服务器

Dart实战——Socks5服务器

作者头像
arcticfox
发布2020-10-29 10:14:24
2.6K0
发布2020-10-29 10:14:24
举报

视频教程

视频教程已上传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剩余的内容为域名,没有\0结尾。
    • 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剩余的内容为域名,没有\0结尾。
    • 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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程之路从0到1 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 视频教程
  • SOCKS5协议
  • Dart实现源码
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档