前言

之所以想开发这个客户端,主要是因为想要尝试了解ssr和vmess的免流原理和时间。

此篇文章仅提供思路,只是尝试,并不提供完整代码,同时本人已放弃开发,因为这个已经有人进局子了。


为什么能够免流

通过obfs的混淆http请求内容,打开网络隧道,实现欺骗运营商自己的请求地址。

前置了解

在golang开启socks进行链接时,通常是使用io.copy等方法,在高级语言中内容置换等底层已经被封装的极为简单。

99dc588f-21fd-4fc9-bd79-47b371a28bc0.png
需要先了解这些前置知识,仅是网络混淆和tun层内容。

本文章绝不会涉及任何翻墙功能设计!


代码组件

如何启动tun层

首先了解什么是tun层:

在计算机网络中,TUN与TAP是操作系统内核中的虚拟网络设备。不同于普通靠硬件网络适配器实现的设备,这些虚拟的网络设备全部用软件实现,并向运行于操作系统上的软件提供与硬件的网络设备完全相同的功能。TAP等同于一个以太网设备,它操作第二层数据包如以太网数据帧。TUN模拟了网络层设备,操作第三层数据包比如IP数据包。操作系统通过TUN/TAP设备向绑定该设备的用户空间的程序发送数据,反之,用户空间的程序也可以像操作硬件网络设备那样,通过TUN/TAP设备发送数据。在后种情况下,TUN/TAP设备向操作系统的网络栈投递(或“注入”)数据包,从而模拟从外部接受数据的过程。

可以看做是一个虚拟网卡。

注意事项:**

  1. tun层只有当有代码启动并设置后才能够启动,人为用普遍系统api并不能构建成功。
  2. tun必须使用socks的链接,他不支持其他协议方法,同时ipv6的udp也同样不支持。
  3. tun层必须设置好路由表,否则并不会完整代理内容
技术上链接tun层

假设你已经连接了各种网络代理协议方法,并生成了socks链接:

    // Tun.DeviceTunWindowsRun("tun", "10.255.0.2", "10.255.0.1", "255.255.255.0", "socks5://127.0.0.1:809",
    //     []string{"8.8.8.8", "8.8.4.4"}, false)

这个是我封装的方法,具体内容在:Github

此仓库只是将一个tun的具体代码封装罢了,只需要看其中的tun内容。

当你能够让socks5正常通过tun层进行通信时,就可以具体了解混淆内容了。


如何进行网络混淆,欺骗运营商

// HTTPObfs 魔改实现的obfs混淆
/*
 from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/http.go
*/
// HTTPObfs is shadowsocks http simple-obfs implementation
type HTTPObfs struct {
    net.Conn
    host          string
    port          string
    buf           []byte
    offset        int
    firstRequest  bool
    firstResponse bool
}

func (ho *HTTPObfs) Read(b []byte) (int, error) {
    if ho.buf != nil {
        n := copy(b, ho.buf[ho.offset:])
        ho.offset += n
        if ho.offset == len(ho.buf) {
            ho.buf = nil
        }
        return n, nil
    }

    if ho.firstResponse {
        buf := utils.GetBytes(utils.DefaultSize)
        defer utils.PutBytes(buf)
        n, err := ho.Conn.Read(buf)
        if err != nil {
            // utils.BuffPool(utils.DefaultSize).Put(&(buf))
            return 0, err
        }
        idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
        if idx == -1 {
            // utils.BuffPool(utils.DefaultSize).Put(&(buf))
            return 0, io.EOF
        }
        ho.firstResponse = false
        length := n - (idx + 4)
        n = copy(b, buf[idx+4:n])
        if length > n {
            ho.buf = buf[:idx+4+length]
            ho.offset = idx + 4 + n
        } else {
            // utils.BuffPool(utils.DefaultSize).Put(&(buf))
        }
        return n, nil
    }
    return ho.Conn.Read(b)
}

func (ho *HTTPObfs) Write(b []byte) (int, error) {
    if ho.firstRequest {
        req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
        req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
        req.Header.Set("Upgrade", "websocket")
        req.Header.Set("Connection", "Upgrade")
        req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
        req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString([]byte(time.Now().String()[:16])))
        req.ContentLength = int64(len(b))
        err := req.Write(ho.Conn)
        ho.firstRequest = false
        return len(b), err
    }

    return ho.Conn.Write(b)
}

// newHTTPObfs return a HTTPObfs
func newHTTPObfs(conn net.Conn, host string, port string) net.Conn {
    return &HTTPObfs{
        Conn:          conn,
        firstRequest:  false,
        firstResponse: false,
        host:          host,
        port:          port,
    }
}

var _ proxy.Proxy = (*httpOBFS)(nil)

type httpOBFS struct {
    host string
    port string
    p    proxy.Proxy
}

func NewHTTPOBFS(config *node.Protocol_ObfsHttp) node.WrapProxy {
    return func(p proxy.Proxy) (proxy.Proxy, error) {
        return &httpOBFS{
            host: config.ObfsHttp.Host,
            port: config.ObfsHttp.Port,
            p:    p,
        }, nil
    }
}

func (h *httpOBFS) Conn(s proxy.Address) (net.Conn, error) {
    conn, err := h.p.Conn(s)
    if err != nil {
        return nil, err
    }
    return newHTTPObfs(conn, h.host, h.port), nil
}

func (h *httpOBFS) PacketConn(s proxy.Address) (net.PacketConn, error) {
    return h.p.PacketConn(s)
}

这是其中自行改变的obfs的混淆代码。

具体思路:将socks的读写流进行设置和复制切换,进行欺骗。

在此不得不佩服大家伙的思路,居然想到通过欺骗gfw的普遍方法想到欺骗运营商达到免流。

本人已经完成了自行逐渐免流代理,但是碍于违法,就只是测试,不公开。

最后修改:2023 年 03 月 19 日
如果觉得我的文章对你有用,请随意赞赏