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

news2024/12/23 11:51:35

文章目录

    • 前言
    • socks协议简介
    • go实现一个简单的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 |
    +----+----------+----------+
    

    在这里插入图片描述

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

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

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

    在这里插入图片描述

    socks5–不验证

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

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

    在这里插入图片描述

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

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

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

    在这里插入图片描述

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

  5. socks协议到这里已经结束。后面则是数据转发。由于转发过程是在传输层。所以无论是http还是https,该代理程序都可以很好的运行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875348.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

excel填数据转json格式

定制化比较严重&#xff0c;按需更改 excel文件如下 代码 # -*- coding: utf-8 -*- import oss2 import shutil import sys import xlwt import xlrd import json from datetime import datetime, timedeltafile1 "C:\\Users\\cxy\\Desktop\\generate.xls" #打开表…

【Spring专题】手写简易Spring容器过程分析——引导篇

目录 前言说在前面阅读准备 思路整理手写源码示例一、手写前的准备1.1 注解1.2 测试Bean1.3 调用实例 二、构造方法&#xff08;构建基本流程&#xff09;三、实现scan()方法3.1 doGetScanPackage()&#xff1a;获取扫描路径3.2 doLoadClassFromDiskAndScan()&#xff1a;从电脑…

【100天精通python】Day34:使用python操作数据库_ORM(SQLAlchemy)使用

目录 专栏导读 1 ORM 概述 2 SQLAlchemy 概述 3 ORM&#xff1a;SQLAlchemy使用 3.1 安装SQLAlchemy&#xff1a; 3.2 定义数据库模型类&#xff1a; 3.3 创建数据表&#xff1a; 3.4 插入数据&#xff1a; 3.5 查询数据&#xff1a; 3.6 更新数据&#xff1a; 3.7 删…

24届最新计算机毕业设计选题推荐 -计算机专业毕业设计题目参考大全

大家好&#xff0c;我们是竹林可以&#xff08;计算机毕ye设ji代做团队&#xff09;&#xff0c;大四的同学马上要开始毕业设计开题啦&#xff0c;大家要好好准备哦。 学长学姐们给大家详细整理了计算机毕设最新选题&#xff0c;我们专注毕ye设ji多年&#xff0c;积累了丰富的…

【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基本介绍登场角色应用场景 案例实现案例一类图实现 案例二&#xff1a;借贷平台源码剖析传统方式实现分析状态修改流程类图实现 案例三&#xff1a;金库警报系统系统的运行逻辑伪代码传统实现方式使用状态模式 类图实现分析问题问题一问题二 总结文章说明…

element-ui的el-dialog,简单的封装。

el-dialog是使用率很高的组件 使用el-dialog很多都是按照文档的例子&#xff0c;用一个变量控制是否显示&#xff0c;再来一个变量控制标题。 如果我这个对话框多个地方使用的话还要创建多个变量&#xff0c;甚至关闭之后还要清空一些变量&#xff0c;应该可以简化一点。我写…

数据结构:力扣OJ题(每日一练)

目录 题一&#xff1a;环形链表 思路一&#xff1a; 题二&#xff1a;复制带随机指针的链表 思路一&#xff1a; 本人实力有限可能对一些地方解释的不够清晰&#xff0c;可以自己尝试读代码&#xff0c;望海涵&#xff01; 题一&#xff1a;环形链表 给定一个链表的头节点…

奥威BI数据可视化工具:报表就是平台,随时自助分析

别的数据可视化工具&#xff0c;报表就只是报表&#xff0c;而奥威BI数据可视化工具&#xff0c;一张报表就约等于一个平台&#xff0c;可随时展开多维动态自助分析&#xff0c;按需分析&#xff0c;立得数据信息。 奥威BI是一款多维立体分析数据的数据可视化工具。它可以帮助…

Camtasia2023最新专业的电脑屏幕录制和视频剪辑软件

Camtasia专业的屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和&#xff01;Camtasia是一款功能强大的屏幕录制和视频编辑软件…

办理流量卡也是有条件的,这五种情况就不能办理流量卡!

流量卡资费虽然便宜&#xff0c;但也不是谁都可以办得&#xff0c;以下这几种情况是办不了的&#xff01; 看到网上的流量卡资费便宜&#xff0c;也想随手申请一张&#xff0c;别想得太简单了&#xff0c;流量卡也不是那么好办理的&#xff0c;换句话来讲&#xff0c;办理流量…

【Quarkus技术系列】「云原生架构体系」在云原生时代下的Java“拯救者”是Quarkus,那云原生是什么呢?

云原生时代下的Java"拯救者" 在云原生时代&#xff0c;其实Java程序是有很大的劣势的&#xff0c;以最流行的spring boot/spring cloud微服务框架为例&#xff0c;启动一个已经优化好&#xff0c;很多bean需要lazy load的application至少需要3-4秒时间&#xff0c;内…

Chrome小技巧---多用户登录同一网站不串信息

测试中经常需要用到浏览器需要登录多个账号 但是有一个问题就是会串号 通过添加不同的用户再用这用户登录&#xff0c;就不串号了&#xff1a; 还可以在浏览器的偏好设置中添加启动地址 这样每次打开&#xff0c;就进到设置的地址中了

如何将阿里云WiredTiger引擎的MongoDB物理备份文件恢复至自建数据库

数据库操作一直是一个比较敏感的话题&#xff0c;动不动“删库跑路”&#xff0c;可见数据库操作对于一个项目而言是非常重要的&#xff0c;我们有时候会因为一个游戏的严重bug或者运营故障要回档数据库&#xff0c;而你们刚好使用的是阿里云的Mongodb&#xff0c;那么这篇文章…

用ThreadLocal做链路追踪(演变升级版)

前言 1、ThreadLocal是线程变量&#xff0c;线程之间彼此隔离&#xff0c;天生线程安全。因为它是跟着线程走的&#xff0c;考虑到这点&#xff0c;它很适合做链路追踪&#xff08;TraceId&#xff09; 2、当我们写的接口接收到其它地方&#xff08;可能是前端、也可能是其它服…

python如何开发一个电商进销存管理系统?

让我们来看一下题主的需求&#xff1a; 管理公司的淘宝天猫平台&#xff0c;后端仓库&#xff0c;采购进行数据同步。其中最主要的还是要对接淘宝API &#xff0c;实现实时订单的通知&#xff0c;同步淘宝订单&#xff0c;管理买家信息&#xff0c;发货&#xff0c;财务统计等…

亚马逊举报差评有什么作用?有没有可以举报差评的软件?

在亚马逊上举报差评具有以下作用&#xff1a; 1、维护信誉和公平性&#xff1a; 举报差评有助于维护亚马逊市场的信誉和公平性。虚假或不当的差评可能会误导其他消费者&#xff0c;影响他们做出购买决策。通过举报这些问题&#xff0c;可以确保评价体现真实的用户体验&#xf…

【SpringCloud技术专题】「Resilience4j入门指南」(1)轻量级熔断框架的入门指南

基础介绍 Resilience4j是一款轻量级&#xff0c;易于使用的容错库&#xff0c;其灵感来自于Netflix Hystrix&#xff0c;但是专为Java 8和函数式编程而设计。轻量级&#xff0c;因为库只使用了Vavr&#xff0c;它没有任何其他外部依赖下。相比之下&#xff0c;Netflix Hystrix…

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

大数据培训前景怎么样?企业需求量大吗

大数据行业对大家来说并不陌生&#xff0c;大数据行业市场人才需求量大&#xff0c;越早入行越有优势&#xff0c;发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力&#xff0c;以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…

落地大模型应知必会(3): 如何构建多任务的LLM应用

编者按&#xff1a;今年以来&#xff0c;大语言模型(LLM)已被广泛应用于各种自然语言处理任务&#xff0c;也越来越多地被用于构建复杂的语言应用。但是构建多任务的 LLM 应用仍面临一定的挑战&#xff0c;需要解决任务组合和调控等问题。 本文内容介绍了构建多任务 LLM 应用可…