GO 的 socks5代理 编写

这里学习一下 socks5 代理的编写

网上有很多 学习一下

go 语言实战入门案例之实现Socks5 - 知乎

滑动验证页面

socks5协议原理学习-腾讯云开发者社区-腾讯云 (tencent.com)

首先我们要了解一下socks5的代理方式

socks5 是基于

  • 认证
  • 建立连接
  • 转发数据

所形成的代理 我们只需要按照这三个写出代码即可 

首先就是socks5的认证

Socks5Atuth

这里首先要认证 那么我们首先要确定是不是socks5代理

 在socks5中 前两个字节 分别是 socks版本号 和 支持的认证方式

前面两个 1

所以这里我们开始读取前面的 版本号 ver socks5是固定值 0x05

这里开始编写一下监听

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdVer = 0x01
const aytpVerIpv4 = 0x01
const aytpVerUrl = 0x03   //这里是下面的常量 不需要理会即可 当作值即可

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	for {
		client, err := server.Accept()
		if err != nil {
			log.Printf("Accpet error :", err)
			continue
		}
		//确定ip端口 进行链接后 开始进程
		fmt.Println("开始监听", client, client.RemoteAddr())
	}
}

 然后开始处理数据

首先我们需要开启一个协程 处理

func process(conn net.Conn) {
	defer conn.Close()
	readio := bufio.NewReader(conn)
	err := auth(readio, conn)
}

这里我们首先处理一下认证的信息

	// +----+----------+----------+
	// |VER | NMETHODS | METHODS  |
	// +----+----------+----------+
	// | 1  |    1     | 1 to 255 |
	// +----+----------+----------+
	// VER: 协议版本,socks5为0x05
	// NMETHODS: 支持认证的方法数量
	// METHODS: 对应NMETHODS,NMETHODS的值为多少,METHODS就有多少个字节。RFC预定义了一些值的含义,内容如下:
	// X’00’ NO AUTHENTICATION REQUIRED
	// X’02’ USERNAME/PASSWORD

	// 版本和NMETHODS值都是单字节的,所以ReadByte读一个字节就好了

这里需要认证的东西就是这些 我们首先进行认证 ver

func auth(readio *bufio.Reader, conn net.Conn) (err error) {
	ver, err := readio.ReadByte()
	fmt.Println(ver)
	return nil
}

这里是通过 readbyte 读取一个字节 我们输出一下就知道是多少了

发现我们通过浏览器进行代理 第一个byte是 5 对应 socks5

如果将代理设置为 socks4 那么这里就是对应4

 然后这里进行匹配 如果不是就输出错误 这样 ver认证就结束了

认证的ver

func auth(readio *bufio.Reader, conn net.Conn) (err error) {
	ver, err := readio.ReadByte()
	if err != nil {
		return fmt.Errorf("ver error:", err)
	}
	if ver != socks5Ver {
		return fmt.Errorf("ver num error: ", err)
	}

}

认证method        

这里是socks5的认证,是否需要认证

	methodSize, err := readio.ReadByte()
	fmt.Println(methodSize)
	return

其实这里就是读取一个字节的大小

func auth(readio *bufio.Reader, conn net.Conn) (err error) {
	ver, err := readio.ReadByte()
	if err != nil {
		return fmt.Errorf("ver error:", err)
	}
	if ver != socks5Ver {
		return fmt.Errorf("ver num error: ", err)
	}
	methodSize, err := readio.ReadByte() //确定切片大小 为 1字节
	method := make([]byte, methodSize)   //创建一个大小的切片
	_, err = io.ReadFull(readio, method) // 这里是判断是否读了
	if err != nil {
		return fmt.Errorf("read method error :", err)
	}
	_, err = conn.Write([]byte{socks5Ver, 0x00}) // 这里是握手的回复 说明我们不需要认证
	if err != nil {
		return fmt.Errorf("write error:", err)
	}
	return nil
}

这里注意

_, err = conn.Write([]byte{socks5Ver, 0x00}) 

这里其实是监听后告诉 不需要进行 认证

当我们认证完后开始处理浏览器的报文

Socks5Connect

	// 读取浏览器发送的报文
	// +----+-----+-------+------+----------+----------+
	// |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
	// +----+-----+-------+------+----------+----------+
	// | 1  |  1  | X'00' |  1   | Variable |    2     |
	// +----+-----+-------+------+----------+----------+
	// VER 版本号,socks5的值为0x05
	// CMD 0x01表示CONNECT请求
	// RSV 保留字段,值为0x00
	// ATYP 目标地址类型,DST.ADDR的数据对应这个字段的类型。
	//   0x01表示IPv4地址,DST.ADDR为4个字节
	//   0x03表示域名,DST.ADDR是一个可变长度的域名
	// DST.ADDR 一个可变长度的值
	// DST.PORT 目标端口,固定2个字节


这里是浏览器发送的报文 我们依旧先进行鉴定
func connnect(readio *bufio.Reader, conn net.Conn) (err error) {
	buf := make([]byte, 4)
	_, err = io.ReadFull(readio, buf) // 这里是读取字节数 读取前面4个作为一个切片
	if err != nil {
		return fmt.Errorf("read header error :", err)
	}
	var ver, cmd, atyp = buf[0], buf[1], buf[3] // 这里对应报文的字节认证的位置
	fmt.Println(ver, cmd, atyp)
	return
}

这里就很明显了 5 对应 socks5 1 对应 链接的请求 3 对应 url 是一个域名

+----+--------+-------------------+--------------+---------------+
|VER |  CMD   |       ATYP        |   HOST SIZE  |      HOST     |
+----+--------+-------------------+--------------+---------------+
| 05 |  01    |       03          |      11      | www.exa.com   |
+----+--------+-------------------+--------------+---------------+


首先之前的都已经被读了


ver 

cmd

atyp

然后再读 就是 host size 这里是 后面 host 的地址长度

这里开源发现 cn.bing.com的长度是11 因为url是可变的 所以后面的host也是可变的

我们首先读取字节长度 然后作为 值传递给切片 读取该长度的值 这样我们就开源获取到url地址

	_, err = io.ReadFull(readio, buf[:2]) //再向后读取两个字节 是port
	if err != nil {
		return fmt.Errorf("port error:", err)
	}
	port := binary.BigEndian.Uint16(buf[:2])  //这里是大端序监听端口
	fmt.Println(port)
	fmt.Println(addr)
	return

这里的原理是这样的

首先我们读取两个字节

443 [1 187]

然后进行大端序排序 0 在高 80 在低

这个时候就是

[1 187]

然后我们进uint16处理

[1 187]

将高位字节(1)左移 8 位,使其占据 uint16 的高 8 位,得到结果 00000001 00000000

将低位字节(187)放在 uint16 的低 8 位,得到结果 00000001 10111011

 00000001 10111011

443

这样我们就获取到了端口

然后我们就可以进行拼接url

	dest := fmt.Sprintf("%v:%v", addr, port)  
	fmt.Println(dest)

 然后我们开始建立tcp请求

	dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port)) //建立tcp协议
	if err != nil {
		return fmt.Errorf("dial error:", err)
	}
	defer dest.Close()
	log.Println("访问:", addr, port)

完整代码

package main

import (
	"bufio"
	"context"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"net"
)

const socks5Ver = 0x05
const cmdVer = 0x01
const aytpVerIpv4 = 0x01
const aytpVerUrl = 0x03

func main() {
	server, err := net.Listen("tcp", "127.0.0.1:1080")
	if err != nil {
		panic(err)
	}
	for {
		client, err := server.Accept()
		if err != nil {
			log.Printf("Accpet error :", err)
			continue
		}
		//确定ip端口 进行链接后 开始进程
		fmt.Println("开始监听")
		go process(client)
	}
}
func process(conn net.Conn) {
	defer conn.Close()
	readio := bufio.NewReader(conn)
	err := auth(readio, conn)
	if err != nil {
		fmt.Errorf("ip: %v,auth error :", conn.RemoteAddr().String(), err)
	}
	err = connnect(readio, conn)
}

func auth(readio *bufio.Reader, conn net.Conn) (err error) {
	ver, err := readio.ReadByte()
	if err != nil {
		return fmt.Errorf("ver error:", err)
	}
	if ver != socks5Ver {
		return fmt.Errorf("ver num error: ", err)
	}
	methodSize, err := readio.ReadByte() //确定切片大小 为 1字节
	method := make([]byte, methodSize)   //创建一个大小的切片
	_, err = io.ReadFull(readio, method) // 这里是判断是否读了
	if err != nil {
		return fmt.Errorf("read method error :", err)
	}
	_, err = conn.Write([]byte{socks5Ver, 0x00}) // 这里是握手的回复 说明我们不需要认证
	if err != nil {
		return fmt.Errorf("write error:", err)
	}
	return nil
}

func connnect(readio *bufio.Reader, conn net.Conn) (err error) {
	buf := make([]byte, 4)
	_, err = io.ReadFull(readio, buf) // 这里是读取字节数 读取前面4个作为一个切片
	if err != nil {
		return fmt.Errorf("read header error :", err)
	}
	var ver, cmd, atyp = buf[0], buf[1], buf[3] // 这里对应报文的字节认证的位置
	if ver != socks5Ver {
		return fmt.Errorf("connect ver error:", err)
	}
	if cmd != cmdVer {
		return fmt.Errorf("connect cmd	 error:", err)
	}
	addr := ""
	switch atyp {
	case aytpVerIpv4:
		_, err = io.ReadFull(readio, buf)
		if err != nil {
			return fmt.Errorf("ipv4 error:", err)
		}

		addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3]) //读取ip地址
	case aytpVerUrl: //这里解析的是url
		hostSize, err := readio.ReadByte() //获取url的字节长度
		if err != nil {
			return fmt.Errorf("read hostSize failed:%w", err)
		}
		host := make([]byte, hostSize) //获取url字符的字节
		_, err = io.ReadFull(readio, host)
		if err != nil {
			return fmt.Errorf("read host failed:%w", err)
		}
		addr = string(host)
	default:
		return fmt.Errorf("not yet", atyp)
	}

	_, err = io.ReadFull(readio, buf[:2]) //再向后读取两个字节 是port
	if err != nil {
		return fmt.Errorf("port error:", err)
	}
	port := binary.BigEndian.Uint16(buf[:2])                       //这里是大端序监听端口
	dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port)) //建立tcp协议
	if err != nil {
		return fmt.Errorf("dial error:", err)
	}
	defer dest.Close()
	log.Println("访问:", addr, port)
	_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
	if err != nil {
		return fmt.Errorf("写入错误:", err)
	}
	ctx, cancel := context.WithCancel(context.Background()) //启动一个可以取消的上下文功能
	defer cancel()
	go func() {

		_, _ = io.Copy(dest, readio) //这里是从我们的请求中读取出来 写入请求中
		cancel()
	}()
	go func() {
		_, _ = io.Copy(conn, dest) //这里从请求中写入返回报文中
		cancel()
	}()
	<-ctx.Done() // 这里是 只要管道输出内容了就停止 所以上面两个协程 只要有一个输出 就取消
	return nil
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【前端小点】谷歌地图MarkerClusterer,点分类聚合

谷歌地图点分类聚合 本篇文章记录&#xff0c;如何在谷歌地图中进行点聚合&#xff0c;并分类进行聚合&#xff0c;如何修改聚合后的聚合样式。 之前有一篇博文是记录如何在vue中使用谷歌地图&#xff0c;可参考&#xff0c;传送门&#xff1a; vue中使用谷歌地图绘制一个或多…

Echarts自定义样式实现3D柱状图-长方体-圆柱体,两种样式

Echarts自定义样式实现3D柱状图-长方体-圆柱体&#xff0c;两种样式 效果图代码series配置项目 效果图 长方体 柱状体 代码 <!--此示例下载自 https://echarts.apache.org/examples/zh/editor.html?cbar3d-dataset&gl1 --> <!DOCTYPE html> <html lang…

vp与vs联合开发-通过FrameGrabber连接相机

添加控件 1.CogRecordDisplay 控件 用于显示图像 初始化相机对象方法 //启动窗体时 调用初始化相机方法 //封装相机关闭方法 //窗体关闭时 调用相机关闭方法 拍照 设置采图事件 // 保存图像 设置曝光按钮事件 1.可变参数

C语言——完数难题

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 生命如同寓言&#xff0c;其价值不在于…

智能 GPT 图书馆又重生了

智能 GPT 图书馆又重生了 作者&#xff1a;程序员小白条 1&#xff09;概述 自从大二寒假准备开始筹备这个项目&#xff0c;到现在已经一年了&#xff0c;这个项目能维护一年&#xff0c;不愧是我.jpg。本来这个项目只是想练练手&#xff0c;因为那时候刚学完 Spring Boot2 V…

Qt实现动画的2种方式

由于我之前是写java的所以在学习Qt的时候感觉会有点熟悉&#xff0c;因为Qt就是 用c写&#xff0c;而java底层也是c实现的 先看效果&#xff1a; 一、使用QMovie 这种方式我目前是用来加载gif图的&#xff0c;很简单噢&#xff0c;只不过我是加载的本地的路径&#xff0c;如…

中国1KM分辨率年均降水量数据集(1901-2022)

数据名称&#xff1a;中国1KM分辨率年均降水量数据集&#xff08;1901-2022&#xff09; 数据时间&#xff1a;1901年至今 数据空间位置&#xff1a;全国 数据格式&#xff1a;tiff 数据空间分辨率&#xff1a;1kM 数据坐标系&#xff1a;WGS1984 数据简介&#xff1a;该…

【设计模式--行为型--访问者模式】

设计模式--行为型--访问者模式 访问者模式定义结构案例优缺点使用场景扩展分派动态分派静态分派双分派 访问者模式 定义 封装一些作用于某种数据结构中的各元素的操作&#xff0c;它可以在不改变这个数据结构的前提下定义作用于这些元素的新操作。 结构 抽象访问者角色&…

【Java异常】idea 报错:无效的目标发行版:17 的解决办法

【Java异常】idea 报错&#xff1a;无效的目标发行版&#xff1a;17 的解决办法 一&#xff0c;问题来源 springcloud的第一个demo项目就给我干趴了 二、原因分析 java: 无效的目标发行版: 17 原因就是 JDK 版本不对。从 IDEA 编辑器中可以找到问题的原因所在&#xff0c;…

Linux--fork创建子进程详解

目录 一.初识fork函数 二.fork的返回值 三.fork原理 1.fork是如何创建子进程的&#xff1f; 2.为什么fork会有两个返回值&#xff1f; 3.为什么父进程的返回值是子进程的pid&#xff0c;子进程返回值是0&#xff1f; 4.fork之后&#xff0c;父子进程谁先运行&#xff1f;…

【Python 基础】-- 在 mac OS 中安装 多个 python 版本

目录 1、需求 2、实现 2.1 安装 pyenv 2.2 安装 pyenv-virtualenv 2.3 配置环境变量 2.4 创建 python 3.9.9 的环境 2.5 激活环境&#xff0c;在当前项目目录中使用&#xff0c;即执行 python 1、需求 由于项目所依赖的 python 版本有多个&#xff0c;需要在不同的 pyth…

axios请求封装

http.js // untils / http.js //导入封装好的axios实例 import request from ./requestconst http {get(url, params) {const config {method: get,url: url}if (params) config.params paramsreturn request(config)},post(url, params) {const config {method: post,url…

深入浅出RPC:选取适合自己的RPC

文章目录 1、RPC概念&&背景1.1、RPC背景 1.2、RPC是什么&#xff0c;什么时候需要用到&#xff1f;2、进程间的通信 - IPC与RPC2.1、什么是IPC2.2、IPC与RPC联系 3、RPC的实现3.1、RPC实现的基本思路3.2、RPC实现的扩展方向 4、RPC的选择 1、RPC概念&&背景 1.…

Elasticsearch——快速入门

从零基础的内容开始介绍Elasticsearch&#xff0c;主要包含以下内容&#xff1a; Elasticsearch的定义、优点&#xff0c;以及典型的业务场景。Elasticsearch中重要的概念。Elasticsearch典型的接入方式。安装Elasticsearch。使用Kibana调试Elasticsearch。Elasticsearch节点的…

【LeetCode刷题笔记(8-3)】【Python】【接雨水】【双指针】【困难】

文章目录 引言接雨水题目描述提示 解决方案3&#xff1a;【双指针】结束语 接雨水 【LeetCode刷题笔记&#xff08;8-1&#xff09;】【Python】【接雨水】【动态规划】【困难】 【LeetCode刷题笔记&#xff08;8-2&#xff09;】【Python】【接雨水】【单调栈】【困难】 引言…

从如何使用到如何实现一个Promise

promise是什么&#xff1f;主要用来解决什么问题&#xff1f; Promise是异步编程的一种解决方案&#xff0c;比传统解决方案--回调函数和事件--更合理更强大。 Promise特点&#xff1a; &#xff08;1&#xff09;对象的状态不受外界影响。Promise对象代表一个异步操作&…

ModuleNotFoundError: No module named ‘openai.error‘

ModuleNotFoundError: No module named ‘openai.error’ result self.fn(*self.args, **self.kwargs) File “H:\chatGPTWeb\chatgpt-on-wechat\channel\chat_channel.py”, line 168, in _handle reply self._generate_reply(context) File “H:\chatGPTWeb\chatgpt-on-wec…

2023_Spark_实验二十九:Flume配置KafkaSink

实验目的&#xff1a;掌握Flume采集数据发送到Kafka的方法 实验方法&#xff1a;通过配置Flume的KafkaSink采集数据到Kafka中 实验步骤&#xff1a; 一、明确日志采集方式 一般Flume采集日志source有两种方式&#xff1a; 1.Exec类型的Source 可以将命令产生的输出作为源&…

性能加速包: SpringBoot 2.7JDK 17,你敢尝一尝吗 | 京东物流技术团队

前言 众所周知&#xff0c;SpringBoot3.0迎来了全面支持JDK17的局面&#xff0c;且最低支持版本就是JDK17&#xff0c;这就意味着&#xff0c;Spring社区将完全抛弃JDK8&#xff0c;全面转战JDK17。作为JAVA开源生态里的扛把子&#xff0c;Spring可以说是整个JAVA生态的风向标…

(8)Linux Makefile | 依赖关系,依赖方法

&#x1f4ad;前言&#xff1a; 本篇文章会着重讲解Linux中的自动化构建代码工具: make/makefile的介绍与使用。 在Linux下编译代码时,每次都会输入 gcc code.c -o code.exe在删除可执行程序时,每次都会输入 rm -rf code.exe这样非常的不方便,很麻烦,于是乎学习自动化构建代…