GOST 应用与分析(一)(未完待续)

GOST 项目的介绍及 TUNNEL 实现分析

Posted by pandaychen on January 21, 2024

0x00 前言

gost 是一个非常有意思的项目,在笔者看来,像是乐高积木一样的代理连接器(多机端口转发器),其核心概念是四大模块:

  • Service:Service 是指一个网络服务,它可以是一个服务器或者一个客户端。每一个 service 都有一个特定的网络地址和网络协议,如 HTTP,SOCKS5 等。GOST 通过 service 来接收和发送网络数据
  • Node:Node 代表一个代理服务器。一个 node 包含了代理服务器的地址、端口和协议等信息。一个 Service 可以由一个或多个 node 组成
  • Hop:Hop 是指在 GOST 中数据传输过程中,从一个 node 到另一个 node 的跳转。每个 hop 都有一个特定的转发规则,例如从一个 HTTP 代理跳转到一个 SOCKS5 代理
  • Chain:Chain 是指一个网络服务链路。一个 chain 可以包含多个 hop,数据在这些 hop 之间按照特定的顺序进行转发。通过 chain,GOST 可以实现数据的多次转发,从而实现复杂的网络代理功能

本文分析版本基于 v3.0.0-nightly.20240201,地址 gost-3.0.0

ARCH

gost 源码的设计(结构)极佳,适合 golang 进阶使用

0x01 支持 tunnel 场景

GOST 作为隧道 tunnel 有三种主要使用方式:

正向代理

作为代理服务访问网络,可以组合使用多种协议组成转发链进行转发 forwarder

端口转发

将一个服务的端口映射到另外一个服务的端口,同样可以组合使用多种协议组成转发链进行转发 portforwarding

反向代理

利用隧道和内网穿透将内网服务暴露到公网访问 reverse

先看一个简单的 例子

代理模式

1、代理 + 转发:监听在 8080 端口的 HTTP 代理服务,使用 192.168.1.1:8080 做为上级代理进行转发

services:
- name: service-0
  addr: ":8080"
  handler:
    type: http
    chain: chain-0
  listener:
    type: tcp
chains:
- name: chain-0
  hops:
  - name: hop-0
    nodes:
    - name: node-0
      addr: 192.168.1.1:8080
      connector:
        type: http
      dialer:
        type: tcp

2、使用多级转发(转发链):GOST 按照设置 hop-0hop-1 的顺序将请求最终转发给 192.168.1.2:1080 处理

services:
- name: service-0
  addr: ":8080"
  handler:
    type: auto
    chain: chain-0
  listener:
    type: tcp
chains:
- name: chain-0
  hops:
  - name: hop-0
    nodes:
    - name: node-0
      addr: 192.168.1.1:8080
      connector:
        type: http
      dialer:
        type: tcp
  - name: hop-1
    nodes:
    - name: node-0
      addr: 192.168.1.2:1080
      connector:
        type: socks5
      dialer:
        type: tcp

转发模式

1、TCP 本地端口转发:将本地的 TCP 端口 8080 映射到 192.168.1.180 端口,即所有到本地 8080 端口的数据会被转发到 192.168.1.1:80

services:
- name: service-0
  addr: :8080
  handler:
    type: tcp
  listener:
    type: tcp
  forwarder:
    nodes:
    - name: target-0
      addr: 192.168.1.1:80

2、UDP 本地端口转发:将本地的 UDP 端口 10053 映射到 192.168.1.153 端口,所有到本地 10053 端口的数据会被转发到 192.168.1.1:53

services:
- name: service-0
  addr: :10053
  handler:
    type: udp
  listener:
    type: udp
  forwarder:
    nodes:
    - name: target-0
      addr: 192.168.1.1:53

3、TCP 本地端口转发(转发链):将本地的 TCP 端口 8080 通过转发链 chain-0 映射到 192.168.1.180 端口

services:
- name: service-0
  addr: :8080
  handler:
    type: tcp
    chain: chain-0
  listener:
    type: tcp
  forwarder:
    nodes:
    - name: target-0
      addr: 192.168.1.1:80
chains:
- name: chain-0
  hops:
  - name: hop-0
    nodes:
    - name: node-0
      addr: 192.168.1.2:1080
      connector:
        type: socks5
      dialer:
        type: tcp

4、TCP 远程端口转发:在 192.168.1.2 上开启并监听 TCP 端口 2222,并将 192.168.1.2 上的 2222 端口映射到本地 TCP 端口 22,所有到 192.168.1.2:2222 的数据会被转发到本地端口 22

services:
- name: service-0
  addr: :2222
  handler:
    type: rtcp
  listener:
    type: rtcp
    chain: chain-0
  forwarder:
    nodes:
    - name: target-0
      addr: :22
chains:
- name: chain-0
  hops:
  - name: hop-0
    nodes:
    - name: node-0
      addr: 192.168.1.2:1080
      connector:
        type: socks5
      dialer:
        type: tcp

5、UDP 远程端口转发:在 192.168.1.2 上开启并监听 UDP 端口 10053,并将 192.168.1.2 上的 10053 端口映射到本地 UDP 端口 53,所有到 192.168.1.2:10053 的数据会被转发到本地端口 53

services:
- name: service-0
  addr: :10053
  handler:
    type: rudp
  listener:
    type: rudp
    chain: chain-0
  forwarder:
    nodes:
    - name: target-0
      addr: :53
chains:
- name: chain-0
  hops:
  - name: hop-0
    nodes:
    - name: node-0
      addr: 192.168.1.2:1080
      connector:
        type: socks5
      dialer:
        type: tcp

0x02 tunnel 实现

本小节分析下 gost 项目 的 tunnel 实现,所谓 tunnel,就是通过此传输一些其他协议的数据(变换协议)或者加速访问(和 frp 功能类似),可以抽象为如下模型:

tunnel

公共接口

gost 的 core 目录下面,定义了项目中所有的公共接口 interface 和重要结构体,重要的如下:

  • admission:负责处理连接接入控制,例如限制并发连接数等
  • auth:负责处理用户认证,例如用户名和密码验证
  • bypass:负责处理直连规则,判断某个请求是否需要直接连接,而不经过代理
  • chain:负责管理代理链路,包括多个节点(node)和跳转(hop)的顺序
  • common:包含一些通用的工具和功能,例如解析配置文件、处理命令行参数等
  • connector:处理已经建立的网络连接,例如协议升级、设置连接属性等
  • dialer:负责建立网络连接,创建新的网络连接
  • handler:负责处理网络请求,例如 HTTP 请求、SOCKS5 请求等
  • hop:表示数据传输过程中,从一个节点(node)到另一个节点(node)的跳转
  • hosts:负责处理自定义的域名解析规则,例如将某个域名解析为指定的 IP 地址
  • ingress:负责处理入站连接,例如监听端口、接收客户端连接等
  • limiter:负责处理流量控制,例如限制每个连接的速度、限制总流量等
  • listener:负责监听网络端口,接收客户端连接
  • logger:负责处理日志记录,输出调试信息和错误信息
  • metrics:负责收集和处理统计信息,例如连接数、流量等
  • metadata:负责处理元数据,例如连接的目标地址、协议类型等
  • observer:负责实现观察者模式,让其他组件可以订阅和接收事件通知
  • recorder:负责记录连接信息,例如连接时间、流量等
  • registry:负责管理各种组件的注册和查找,例如注册新的协议处理器、查找已注册的处理器等
  • resolver:负责处理域名解析,将域名转换为 IP 地址
  • router:负责处理路由规则,判断某个请求应该通过哪个代理节点(node)
  • sd:负责处理服务发现,例如从 Consul、Etcd 等服务发现系统获取代理节点信息
  • selector:负责实现负载均衡策略,例如轮询、随机等
  • service:表示一个网络服务,例如 HTTP 代理、SOCKS5 代理等

Dialer VS Connector

DialerConnector 通常会配合使用,例如当 GOST 需要建立一个新的网络连接时,它会首先使用 DialerDial 方法创建一个新的网络连接,然后再使用 ConnectorConnect 方法对这个新建的连接进行处理。这种设计使得 GOST 在处理网络连接时既灵活又高效。

  • DialerDialer 是用于建立网络连接的接口。它定义了一个 Dial 方法,该方法接受一个网络地址,然后返回一个已经建立好的网络连接。Dialer 可以看作是一个网络连接的生产者,它的任务就是创建新的网络连接
  • ConnectorConnector 则是用于处理已经建立的网络连接的接口。它定义了一个 Connect 方法,该方法接受一个已经建立好的网络连接,然后对这个连接进行一些后续处理,比如进行协议升级、设置连接属性等。Connector 可以看作是一个网络连接的消费者,它的任务就是处理已经建立好的网络连接

代码定义

1、Service:通用实例化在 x/servicedefaultService 为默认的 Service 实例化

type Service interface {
	Serve() error
	Addr() net.Addr
	Close() error
}

type defaultService struct {
	name     string
	listener listener.Listener  // 关联 Listener 接口
	handler  handler.Handler       // 关联 Handler 接口
	status   *Status
	options  options
}

任何支持的 Service,都可以通过此通用框架实现,只需要传入相关的 listener 和 handler 即可:

func NewService(name string, ln listener.Listener, h handler.Handler, opts ...Option) service.Service

2、Listener:gost 支持多种 listener 实现,均在 ,定义 在此

// Listener is a server listener, just like a net.Listener.
type Listener interface {
	Init(metadata.Metadata) error
	Accept() (net.Conn, error)
	Addr() net.Addr
	Close() error
}

3、Handler:所有 Handler 的实例化都在 Handler 的主要作用是处理 net.Conn,有几种典型的处理逻辑:

  • CONNECT 代理
  • 仅建 tunnel,将 net.Conn 和另外一个 net.Conn pipe 起来,进作为隧道 tunnel 转发数据
type Handler interface {
	Init(metadata.Metadata) error
	Handle(context.Context, net.Conn, ...HandleOption) error
}

4、Node:实例化单个节点 封装,比如隧道路径上的某个节点

// Node:实例化单个节点封装
type Node struct {
	Name    string
	Addr    string
	marker  selector.Marker
	options NodeOptions
}

5、RouteRoute 被抽象为一组 Node,同时需要至少提供 DialBind 方法的实现

  • Bind:用来实现一个服务的 listener
  • Dial:用来把一组 Node 进行打洞,提供最终的 net.Conn
// Route:Chain 的通用封装,定义了 3 个方法
type Route interface {
	Dial(ctx context.Context, network, address string, opts ...DialOption) (net.Conn, error)
	Bind(ctx context.Context, network, address string, opts ...BindOption) (net.Listener, error)
	Nodes() []*Node // 返回当前的 Node(按顺序)
}

补充下,gost 提供了默认的 Route 实例化实现,代码 有点意思,可以详细阅读下

// Router:是 Route 的实例化,实现了 Bind/Dial/Nodes 方法
type Router struct {
	options RouterOptions
}

实例化的 Router 提供的 Bind实现、以及 Dial实现,下面再做分析

6、Chainer:Chainer 抽象为根据目的 IP(参数),构造出一条可以通的 Route

type Chainer interface {
	Route(ctx context.Context, network, address string, opts ...RouteOption) Route
}

7、Metadata

type Metadata interface {
	IsExists(key string) bool
	Set(key string, value any)
	Get(key string) any
}

8、Binder

type Binder interface {
	Bind(ctx context.Context, conn net.Conn, network, address string, opts ...BindOption) (net.Listener, error)
}

9、Connector && Handshaker

// Connector is responsible for connecting to the destination address.
type Connector interface {
	Init(metadata.Metadata) error
	Connect(ctx context.Context, conn net.Conn, network, address string, opts ...ConnectOption) (net.Conn, error)
}

type Handshaker interface {
	Handshake(ctx context.Context, conn net.Conn) (net.Conn, error)
}

10、Dialer && Handshaker

// Transporter is responsible for dialing to the proxy server.
type Dialer interface {
	Init(metadata.Metadata) error
	Dial(ctx context.Context, addr string, opts ...DialOption) (net.Conn, error)
}

type Handshaker interface {
	Handshake(ctx context.Context, conn net.Conn, opts ...HandshakeOption) (net.Conn, error)
}

11、ingress

type Rule struct {
	// Hostname is the hostname match pattern, e.g. example.com, *.example.org or .example.com.
	Hostname string
	// Endpoint is the tunnel ID for the hostname.
	Endpoint string
}

type Ingress interface {
	// SetRule adds or updates a rule for the ingress.
	SetRule(ctx context.Context, rule *Rule, opts ...Option) bool
	// GetRule queries a rule by host.
	GetRule(ctx context.Context, host string, opts ...Option) *Rule
}

12、Resolver,定义用于 DNS解析

type Resolver interface {
	// Resolve returns a slice of the host's IPv4 and IPv6 addresses.
	// The network should be 'ip', 'ip4' or 'ip6', default network is 'ip'.
	Resolve(ctx context.Context, network, host string, opts ...Option) ([]net.IP, error)
}

实例化

上一小节中抽象的 interface 的实例化都在这个 目录

1、Service

2、Listener:实例化代码,提供了非常多的实现

3、Node/Router/Chain

// route 是x/core/ Router的实例化
type route struct {
	nodes   []*chain.Node // Node is interface
	options RouteOptions
}

// Chain的实例化实现:包含了多个Hop
type Chain struct {
	name     string
	hops     []hop.Hop
	marker   selector.Marker
	metadata metadata.Metadata
	logger   logger.Logger
}

Chain的几个重要方法如下:AddHopRoute,前者用于构建隧道路径上的某一跳hops,后者用于把这些hops打通

func (c *Chain) AddHop(hop hop.Hop) {
	c.hops = append(c.hops, hop)
}

Chain.Route方法

// 根据c.hops构建rt
func (c *Chain) Route(ctx context.Context, network, address string, opts ...chain.RouteOption) chain.Route {
	if c == nil || len(c.hops) == 0 {
		return nil
	}

	var options chain.RouteOptions
	for _, opt := range opts {
		opt(&options)
	}

	rt := NewRoute(ChainRouteOption(c))

	//根据host构建最终的rt结构 (nodes   []*chain.Node)
	for _, h := range c.hops {
		node := h.Select(ctx,
			hop.NetworkSelectOption(network),
			hop.AddrSelectOption(address),
			hop.HostSelectOption(options.Host),
		)
		if node == nil {
			return rt
		}
		if node.Options().Transport.Multiplex() {
			tr := node.Options().Transport.Copy()
			tr.Options().Route = rt
			node = node.Copy()
			node.Options().Transport = tr
			rt = NewRoute()
		}

		rt.addNode(node)
	}
	return rt
}

0x03 实例化分析:一个例子

多级转发链 为例,看下代码的数据运转流程:

gost -L :8080 -F http://192.168.1.1:8080 -F socks5://192.168.1.2:1080

0x04 参考