go入门实践四-go实现一个简单的tcp-socks5代理服务

这篇具有很好参考价值的文章主要介绍了go入门实践四-go实现一个简单的tcp-socks5代理服务。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。协议在应用层和传输层之间。

本文使用先了解socks协议。然后实现一个socks5的tcp代理服务端。最后,进行抓包验证。

本文完整代码见仓库:laboratory/16-go-socks5


socks协议简介

socks协议相对http和tcp协议,还是比较简单。当然,想要搞明白每个细节,也非一件容易的事情。

关于协议本身的介绍见:rfc1928、RFC 1928 - SOCKS 5 协议中文文档「译」

文档总是枯燥的,可以边看文档边看代码:实战:150行Go实现高性能socks5代理


go实现一个简单的socks5代理

了解协议后,我们来实现一个tcp的socks5服务端代理。

网上有很多这样的示例,见:socks - Search Results - Go Packages

本节的代码参考自:实战:150行Go实现高性能socks5代理 、Subsocks: 用 Go 实现一个 Socks5 安全代理 - Luyu Huang's Blog

完整代码见仓库,下面是主要的代码。

package socks5

import (
    "bufio"
    "encoding/binary"
    "errors"
    "fmt"
    "go-socks5-demo/config"
    "go-socks5-demo/utils"
    "io"
    "net"
    "strconv"

    log "github.com/sirupsen/logrus"
)

const SOCKS5VERSION uint8 = 5

const (
    MethodNoAuth uint8 = iota
    MethodGSSAPI
    MethodUserPass
    MethodNoAcceptable uint8 = 0xFF
)

const (
    RequestConnect uint8 = iota + 1
    RequestBind
    RequestUDP
)

const (
    RequestAtypIPV4       uint8 = iota
    RequestAtypDomainname uint8 = 3
    RequestAtypIPV6       uint8 = 4
)

const (
    Succeeded uint8 = iota
    Failure
    Allowed
    NetUnreachable
    HostUnreachable
    ConnRefused
    TTLExpired
    CmdUnsupported
    AddrUnsupported
)

type Proxy struct {
    Inbound struct {
        reader *bufio.Reader
        writer net.Conn
    }
    Request struct {
        atyp uint8
        addr string
    }
    OutBound struct {
        reader *bufio.Reader
        writer net.Conn
    }
}

func Start() error {
    // 读取配置文件中的监听地址和端口
    log.Debug("socks5 server start")
    listenPort := config.Conf.ListenPort
    listenIp := config.Conf.ListenIp
    if listenPort <= 0 || listenPort > 65535 {
        log.Error("invalid listen port:", listenPort)
        return errors.New("invalid listen port")
    }

    //创建监听
    addr, _ := net.ResolveTCPAddr("tcp", listenIp+":"+strconv.Itoa(listenPort))
    listener, err := net.ListenTCP("tcp", addr)
    if err != nil {
        log.Error("fail in listen port:", listenPort, err)
        return errors.New("fail in listen port")
    }

    // 建立连接
    for {
        conn, _ := listener.Accept()
        go socks5Handle(conn)
    }
}

func socks5Handle(conn net.Conn) {
    proxy := &Proxy{}
    proxy.Inbound.reader = bufio.NewReader(conn)
    proxy.Inbound.writer = conn

    err := handshake(proxy)
    if err != nil {
        log.Warn("fail in handshake", err)
        return
    }
    transport(proxy)
}

func handshake(proxy *Proxy) error {
    err := auth(proxy)
    if err != nil {
        log.Warn(err)
        return err
    }

    err = readRequest(proxy)
    if err != nil {
        log.Warn(err)
        return err
    }

    err = replay(proxy)
    if err != nil {
        log.Warn(err)
        return err
    }
    return err
}

func auth(proxy *Proxy) error {
    /*
        Read
           +----+----------+----------+
           |VER | NMETHODS | METHODS  |
           +----+----------+----------+
           | 1  |    1     | 1 to 255 |
           +----+----------+----------+
    */
    buf := utils.SPool.Get().([]byte)
    defer utils.SPool.Put(buf)

    n, err := io.ReadFull(proxy.Inbound.reader, buf[:2])
    if n != 2 {
        return errors.New("fail to read socks5 request:" + err.Error())
    }

    ver, nmethods := uint8(buf[0]), int(buf[1])
    if ver != SOCKS5VERSION {
        return errors.New("only support socks5 version")
    }
    _, err = io.ReadFull(proxy.Inbound.reader, buf[:nmethods])
    if err != nil {
        return errors.New("fail to read methods" + err.Error())
    }
    supportNoAuth := false
    for _, m := range buf[:nmethods] {
        switch m {
        case MethodNoAuth:
            supportNoAuth = true
        }
    }
    if !supportNoAuth {
        return errors.New("no only support no auth")
    }

    /*
        replay
            +----+--------+
            |VER | METHOD |
            +----+--------+
            | 1  |   1    |
            +----+--------+
    */
    n, err = proxy.Inbound.writer.Write([]byte{0x05, 0x00}) // 无需认证
    if n != 2 {
        return errors.New("fail to wirte socks method " + err.Error())
    }

    return nil
}

func readRequest(proxy *Proxy) error {
    /*
        Read
           +----+-----+-------+------+----------+----------+
           |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
           +----+-----+-------+------+----------+----------+
           | 1  |  1  | X'00' |  1   | Variable |    2     |
           +----+-----+-------+------+----------+----------+
    */
    buf := utils.SPool.Get().([]byte)
    defer utils.SPool.Put(buf)
    n, err := io.ReadFull(proxy.Inbound.reader, buf[:4])
    if n != 4 {
        return errors.New("fail to read request " + err.Error())
    }
    ver, cmd, _, atyp := uint8(buf[0]), uint8(buf[1]), uint8(buf[2]), uint8(buf[3])
    if ver != SOCKS5VERSION {
        return errors.New("only support socks5 version")
    }
    if cmd != RequestConnect {
        return errors.New("only support connect requests")
    }
    var addr string
    switch atyp {
    case RequestAtypIPV4:
        _, err = io.ReadFull(proxy.Inbound.reader, buf[:4])
        if err != nil {
            return errors.New("fail in read requests ipv4 " + err.Error())
        }
        addr = string(buf[:4])
    case RequestAtypDomainname:
        _, err = io.ReadFull(proxy.Inbound.reader, buf[:1])
        if err != nil {
            return errors.New("fail in read requests domain len" + err.Error())
        }
        domainLen := int(buf[0])
        _, err = io.ReadFull(proxy.Inbound.reader, buf[:domainLen])
        if err != nil {
            return errors.New("fail in read requests domain " + err.Error())
        }
        addr = string(buf[:domainLen])
    case RequestAtypIPV6:
        _, err = io.ReadFull(proxy.Inbound.reader, buf[:16])
        if err != nil {
            return errors.New("fail in read requests ipv4 " + err.Error())
        }
        addr = string(buf[:16])
    }
    _, err = io.ReadFull(proxy.Inbound.reader, buf[:2])
    if err != nil {
        return errors.New("fail in read requests port " + err.Error())
    }
    port := binary.BigEndian.Uint16(buf[:2])
    proxy.Request.atyp = atyp
    proxy.Request.addr = fmt.Sprintf("%s:%d", addr, port)
    log.Debug("request is", proxy.Request)
    return nil
}

func replay(proxy *Proxy) error {
    /*
        write
           +----+-----+-------+------+----------+----------+
           |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
           +----+-----+-------+------+----------+----------+
           | 1  |  1  | X'00' |  1   | Variable |    2     |
           +----+-----+-------+------+----------+----------+
    */
    conn, err := net.Dial("tcp", proxy.Request.addr)
    if err != nil {
        log.Warn("fail to connect ", proxy.Request.addr)
        _, rerr := proxy.Inbound.writer.Write([]byte{SOCKS5VERSION, HostUnreachable, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
        if rerr != nil {
            return errors.New("fail in replay " + err.Error())
        }
        return errors.New("fail in connect addr " + proxy.Request.addr + err.Error())
    }
    _, err = proxy.Inbound.writer.Write([]byte{SOCKS5VERSION, Succeeded, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
    if err != nil {
        return errors.New("fail in replay " + err.Error())
    }
    proxy.OutBound.reader = bufio.NewReader(conn)
    proxy.OutBound.writer = conn
    return nil
}

func transport(proxy *Proxy) {
    // 语义上是注释的动作;但是iobuf.reader中无法获取rd值
    // io.Copy(proxy.OutBound.writer, proxy.Inbound.reader)
    go io.Copy(proxy.OutBound.writer, proxy.Inbound.writer) // outbound <- inbound
    // io.Copy(proxy.Inbound.writer, proxy.OutBound.reader)
    go io.Copy(proxy.Inbound.writer, proxy.OutBound.writer) // inbound <- outbound
}


运行与压测

运行测试:可以在浏览器中安装和配置下Proxy SwitchyOmega。插件将http流量转换成socks5,流量通过代理,浏览器可正常上网。访问百度,看B站都没问题。

压测:略。


抓包验证

关于wireshare的使用,可见:wireshark入门指北

  1. 客户端连接到 SOCKS 服务端,发送的协议版本与方法选择消息。

    +----+----------+----------+
    |VER | NMETHODS | METHODS  |
    +----+----------+----------+
    | 1  |    1     | 1 to 255 |
    +----+----------+----------+
    

    golang socks5 代理,# 从头实现一个代理工具,golang,socks5

    socks5–客户端只支持1种方法–不验证

  2. 服务器从给出的方法进行选择,并且发送方法选择消息

    +----+--------+
    |VER | METHOD |
    +----+--------+
    | 1  |   1    |
    +----+--------+
    

    golang socks5 代理,# 从头实现一个代理工具,golang,socks5

    socks5–不验证

  3. 客户端发送要请求的目标地址。

    +----+-----+-------+------+----------+----------+
    |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
    +----+-----+-------+------+----------+----------+
    | 1  |  1  | X'00' |  1   | Variable |    2     |
    +----+-----+-------+------+----------+----------+
    

    golang socks5 代理,# 从头实现一个代理工具,golang,socks5

    客户端,告诉服务端:要请求www.bing.com,目标端口是443。

  4. 服务端根据请求信息,去建立连接。并返回连接情况。

    +----+-----+-------+------+----------+----------+
    |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
    +----+-----+-------+------+----------+----------+
    | 1  |  1  | X'00' |  1   | Variable |    2     |
    +----+-----+-------+------+----------+----------+  
    

    golang socks5 代理,# 从头实现一个代理工具,golang,socks5

    服务单与目标地址连接成功。这里没有设置与目标建立连接的地址和端口。对于UDP而言,这里必须返回。因为客户端与服务端协商完之后,客户端需要将udp的流量发送到这个地址和端口。

  5. socks协议到这里已经结束。后面则是数据转发。由于转发过程是在传输层。所以无论是http还是https,该代理程序都可以很好的运行。文章来源地址https://www.toymoban.com/news/detail-767639.html

到了这里,关于go入门实践四-go实现一个简单的tcp-socks5代理服务的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【字节跳动青训营】后端笔记整理-2 | Go实践记录:猜谜游戏,在线词典,Socks5代理服务器

    **本人是第六届字节跳动青训营(后端组)的成员。本文由博主本人整理自该营的日常学习实践,首发于稀土掘金:🔗Go实践记录:猜谜游戏,在线词典,Socks5代理服务器 | 青训营 我的go开发环境: *本地IDE:GoLand 2023.1.2 *go:1.20.6 猜数字游戏也算是入门一门编程语言必写的程

    2024年02月13日
    浏览(48)
  • 【网络编程】实现一个简单多线程版本TCP服务器(附源码)

    accept 函数是在服务器端用于接受客户端连接请求的函数,它在监听套接字上等待客户端的连接,并在有新的连接请求到来时创建一个新的套接字用于与该客户端通信。 下面是 accept 函数的详细介绍以及各个参数的意义: sockfd: 是服务器监听套接字的文件描述符,通常是使用

    2024年02月13日
    浏览(53)
  • ROS入门与实践--3(工作空间和功能包的创建及HelloWorld简单实现)

    通过上节的学习,我们在ROS文件系统的结构图中了解到了工作空间和功能包,下面我们将详细介绍工作空间的定义,工作空间及功能包的创建流程 简单来说,工作空间就是用来存放开发者的工程开发相关文件的一个文件夹。官方文档给出了两种编译系统,即 Catkin 和 rosbuild

    2024年02月05日
    浏览(37)
  • go入门实践三-go日志库-Logrus入门教程

    日志可以用于排查bug。在C++中,我尝试过:boost log简介、spdlog日志库的封装使用。但我还是比较喜欢plog,因为它简单。 Go 标准库提供了一个日志库log。它的使用可见:Go 每日一库之 log。但是,它有个致命的缺点,没有日志等级。它可以很好的用于日常写demo,但是不适合稍微

    2024年02月13日
    浏览(50)
  • 【Go语言开发】简单了解一下搜索引擎并用go写一个demo

    这篇文章我们一起来了解一下搜索引擎的原理,以及用go写一个小demo来体验一下搜索引擎。 搜索引擎一般简化为三个步骤 爬虫:爬取数据源,用做搜索数据支持。 索引:根据爬虫爬取到的数据进行索引的建立。 排序:对搜索的结果进行排序。 然后我们再对几个专业名词做

    2024年02月16日
    浏览(44)
  • Go-Zero微服务快速入门和最佳实践(一)

    并发编程和分布式微服务 是我们Gopher升职加薪的关键。 毕竟Go基础很容易搞定,不管你是否有编程经验,都可以比较快速的入门Go语言进行简单项目的开发。 虽说好上手,但是想和别人拉开差距,提高自己的竞争力, 搞懂分布式微服务和并发编程还是灰常重要的,这也是我

    2024年04月28日
    浏览(42)
  • 编程小窍门: 一个简单的go mutex的小例子

    本期小窍门用到了两个组件 mutex 这个类似其他语言的互斥锁 waitGroup 这个类似其他语言的信号量或者java的栅栏锁 示例如下

    2024年02月13日
    浏览(42)
  • 用Rust设计一个并发的Web服务:常用Rust库如Tokio、Hyper等,基于TCP/IP协议栈,实现了一个简单的并发Web服务器,并结合具体的代码讲解如何编写并发Web服务器的程序

    作者:禅与计算机程序设计艺术 1994年,互联网泡沫破裂,一批优秀的程序员、工程师纷纷加入到web开发领域。而其中的Rust语言却备受瞩目,它是一种现代系统编程语言,专注于安全和并发。因此,Rust在当下成为最流行的编程语言之一,很多框架也开始使用Rust重构,这使得

    2024年02月06日
    浏览(62)
  • Go语言实现TCP通信

    TCP协议为 传输控制协议 ,TCP协议有以下几个特点: 1. TCP是面向连接的传输层协议; 2. 每条TCP连接只能有两个端点,每条TCP连接是点到点的通信; 3. TCP提供可靠的交付服务,保证传送的数据无差错,不丢失,不重要且有序; 4. TCP提供全双工通信,允许双方在任何时候都能发送

    2023年04月22日
    浏览(34)
  • 使用go语言、Python脚本搭建一个简单的chatgpt服务网站。

    前言 研0在暑假想提升一下自己,自学了go语言编程和机器学习相关学习,但是一味学习理论,终究是枯燥的,于是自己弄点小项目做。 在这之前,建议您需要掌握以下两个技巧,我在这里不赘述了 一个openAI账号,并申请了KEY(b站有教程) 魔法的method(自己摸索哈~网上应该

    2024年02月11日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包