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

文章目录

    • 前言
    • 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 socks5import ("bufio""encoding/binary""errors""fmt""go-socks5-demo/config""go-socks5-demo/utils""io""net""strconv"log "github.com/sirupsen/logrus"
)const SOCKS5VERSION uint8 = 5const (MethodNoAuth uint8 = iotaMethodGSSAPIMethodUserPassMethodNoAcceptable uint8 = 0xFF
)const (RequestConnect uint8 = iota + 1RequestBindRequestUDP
)const (RequestAtypIPV4       uint8 = iotaRequestAtypDomainname uint8 = 3RequestAtypIPV6       uint8 = 4
)const (Succeeded uint8 = iotaFailureAllowedNetUnreachableHostUnreachableConnRefusedTTLExpiredCmdUnsupportedAddrUnsupported
)type Proxy struct {Inbound struct {reader *bufio.Readerwriter net.Conn}Request struct {atyp uint8addr string}OutBound struct {reader *bufio.Readerwriter net.Conn}
}func Start() error {// 读取配置文件中的监听地址和端口log.Debug("socks5 server start")listenPort := config.Conf.ListenPortlistenIp := config.Conf.ListenIpif 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 = connerr := 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 := falsefor _, 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 stringswitch 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 = atypproxy.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 = connreturn 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.mzph.cn/news/36244.shtml

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

相关文章

21款美规奔驰GLS450更换中规高配主机,汉化操作更简单

很多平行进口的奔驰GLS都有这么一个问题&#xff0c;原车的地图在国内定位不了&#xff0c;语音交互功能也识别不了中文&#xff0c;原厂记录仪也减少了&#xff0c;使用起来也是很不方便的。 可以实现以下功能&#xff1a; ①中国地图 ②语音小助手&#xff08;你好&#xf…

运维监控学习笔记8

在服务器端&#xff0c;我们添加了nginx-server的主机&#xff1a; 在解决Error问题的过程中&#xff0c;我还通过zabbix_get这个命令进行了测试&#xff0c;发现是没有的&#xff0c;后来确认是在web页面配置的过程中&#xff0c;我输错了密码。 yum install zabbix-getzabbi…

uniapp-原生地图截屏返回base64-进行画板编辑功能

一、场景 vue写uniapp打包安卓包&#xff0c;实现原生地图截屏&#xff08;andirod同事做的&#xff09;-画板编辑功能 实现效果&#xff1a; 二、逻辑步骤简略 1. 由 原生地图nvue部分&#xff0c;回调返回 地图截屏生成的base64 数据&#xff0c; 2. 通过 uni插件市场 im…

《图解HTTP》——HTTP协议详解

一、HTTP协议概述 HTTP是一个属于应用层的面向对象协议&#xff0c;由于其简捷、快速的方式&#xff0c;适用于分布式超媒体信息系统。它于1990年提出&#xff0c;经过几年的使用与发展&#xff0c;得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版&#xff0c;HTTP…

SQL 语句解析过程详解

SQL 语句解析过程详解&#xff1a; 1&#xff0e;输入SQL语句 2&#xff0e;词法分析------flex 使用词法分析器&#xff08;由Flex生成&#xff09;将 SQL 语句分解为一个个单词&#xff0c;这些单词被称为“标记“。标记包括关键字、标识符、运算符、分隔符等。 2.1 flex 原…

20个互联网用户Python数据分析项目

这篇文章给大家整理了20个互联网用户数据分析的项目。所有收录的项目&#xff0c;进行了严格的筛选&#xff0c;标准有二&#xff1a; 1.有解说性文字&#xff0c;大家能知道每一步在干嘛&#xff0c;新手友好 2.数据集公开&#xff0c;保证大家可以在原文的基础上自行探索 更…

[保研/考研机试] KY96 Fibonacci 上海交通大学复试上机题 C++实现

题目链接&#xff1a; KY96 Fibonacci https://www.nowcoder.com/share/jump/437195121692000803047 描述 The Fibonacci Numbers{0,1,1,2,3,5,8,13,21,34,55...} are defined by the recurrence: F00 F11 FnFn-1Fn-2,n>2 Write a program to calculate the Fibon…

【STM32】FreeRTOS互斥量学习

互斥量&#xff08;Mutex&#xff09; 互斥量又称互斥信号量&#xff08;本质也是一种信号量&#xff0c;不具备传递数据功能&#xff09;&#xff0c;是一种特殊的二值信号量&#xff0c;它和信号量不同的是&#xff0c;它支持互斥量所有权、递归访问以及防止优先级翻转的特性…

人文景区有必要做VR云游吗?如何满足游客出行需求?

VR云游在旅游行业中的应用正在快速增长&#xff0c;为游客带来沉浸式体验的同时&#xff0c;也为文旅景区提供了新的营销方式。很多人说VR全景展示是虚假的&#xff0c;比不上真实的景区触感&#xff0c;人文景区真的有必要做VR云游吗&#xff1f;我的答案是很有必要。 如果你认…

【Pytroch】基于K邻近算法的数据分类预测(Excel可直接替换数据)

【Pytroch】基于K邻近算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.数学公式3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 K最近邻&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种简单但常用的机器…

Redis基础学习

目录 第一章、Redis数据库的下载和安装1.1&#xff09;nosql数据库和 Redis 介绍1.2&#xff09;Windows中下载安装Redis数据库1.3&#xff09;Linux中安装Redis数据库1.4&#xff09;Linux中启动redis1.5&#xff09;Linux中关闭redis 第二章、三种Redis客户端连接Redis数据库…

安全远控如何设置?揭秘ToDesk、TeamViewer 、向日葵安全远程防御大招

写在前面一、远程控制&#xff1a;安全性不可忽略二、远控软件安全设置实测◉ ToDesk◉ TeamViewer◉ 向日葵 三、远控安全的亮点功能四、个人总结与建议 写在前面 说到远程办公&#xff0c;相信大家都不陌生。远程工作是员工在家中或者其他非办公场所上班的一种工作模式&…

传输层协议

传输层协议 再谈端口号端口号范围划分认识知名端口号两个问题netstatpidof UDP协议UDP协议端格式UDP的特点面向数据报UDP的缓冲区UDP使用注意事项基于UDP的应用层协议 TCP协议TCP协议段格式确认应答(ACK)机制超时重传机制连接管理机制理解 CLOSE_WAIT 状态理解TIME_WAIT状态解决…

修改el-select和el-input样式;修改element-plus的下拉框el-select样式

修改el-select样式 .select_box{// 默认placeholder:deep .el-input__inner::placeholder {font-size: 14px;font-weight: 500;color: #3E534F;}// 默认框状态样式更改:deep .el-input__wrapper {height: 42px;background-color: rgba(0,0,0,0)!important;box-shadow: 0 0 0 …

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

如何做好一名网络工程师?具体实践?

预防问题 – 资格与认证 在安装线缆或升级网络时测试线缆是预防问题的有效方式。对已安装布线进行测试的方法有两种。 资格测试确定布线是否有资格执行某些操作 — 换言之&#xff0c;支持特定网络速度或应用。尽管“通过”认证测试也表明按标准支持某一网络速度或应用的能力…

Redux - Redux在React函数式组件中的基本使用

文章目录 一&#xff0c;简介二&#xff0c;安装三&#xff0c;三大核心概念Store、Action、Reducer3.1 Store3.2 Reducer3.3 Action 四&#xff0c;开始函数式组件中使用4.1&#xff0c;引入store4.1&#xff0c;store.getState()方法4.3&#xff0c;store.dispatch()方法4.4&…

深入了解 Rancher Desktop 设置

Rancher Desktop 设置的全面概述 Rancher Desktop 拥有方便、强大的功能&#xff0c;是最佳的开发者工具之一&#xff0c;也是在本地构建和部署 Kubernetes 的最快捷方式。 本文将介绍 Rancher Desktop 的功能和特性&#xff0c;以及 Rancher Desktop 作为容器管理平台和本地…

人工智能原理(2)

目录 一、知识与知识表示 1、知识 2、知识表示 3、知识表示方法 二、谓词逻辑表示法 1、命题逻辑 2、谓词逻辑 三、产生式表达法 1、知识的表示方法 2、产生式系统组成 3、推理方式 4、产生式表示法特点 四、语义网络 1、概念及结构 2、语义网络的基本语义联系 …

zookeeper案例

目录 案例一&#xff1a;服务器动态上下线 服务端&#xff1a; &#xff08;1&#xff09;先获取zookeeper连接 &#xff08;2&#xff09;注册服务器到zookeeper集群&#xff1a; &#xff08;3&#xff09;业务逻辑&#xff08;睡眠&#xff09;&#xff1a; 服务端代码…