Socks5协议入门解析+GO语言简单实现
本文主要介绍了
socks5
协议的构成,和基于go
语言的tcp
实现方案,并且只处理了connent
类型的请求。协议详细地址datatracker.ietf.org/doc/html/rf… 和 datatracker.ietf.org/doc/html/rf…
一、认证
认证方式分为了两种,一种是不用认证,一种是用户名密码认证
- 客户端发送支持的协议数据
属性 | 描述 |
---|---|
VER | 直接取值0x05 ,表示socks5 协议 |
NMETHODS | 客户端支持的认证方式数量 |
METHODS | 认证方式的实际值和NMETHODS 对应 |
- 服务端回复客户端数据
METHOD
支持的值
o X'00' NO AUTHENTICATION REQUIRED
o X'01' GSSAPI
o X'02' USERNAME/PASSWORD
o X'03' to X'7F' IANA ASSIGNED
o X'80' to X'FE' RESERVED FOR PRIVATE METHODS
o X'FF' NO ACCEPTABLE METHODS
不需要密码的方式回复数据
属性 | 描述 |
---|---|
VER | 协议版本,回复0x05 ,表示socks5 协议 |
METHOD | 回复0x00 不需要密码的方式 |
需要密码的方式回复数据
属性 | 描述 |
---|---|
VER | 协议版本,回复0x05 ,表示socks5 协议 |
METHOD | 回复0x02 用户名和密码 |
客户端发送用户名和密码
属性 | 描述 |
---|---|
VER | 协议版本,0x05 ,表示socks5 协议 |
ULEN | 用户名长度 |
UNAME | 用户名 |
PLEN | 密码长度 |
PASWD | 密码 |
服务端回复
属性 | 描述 |
---|---|
VER | 协议版本,0x05 ,表示socks5 协议 |
STATUS | 0x00 表示成功,其他的回复都是失败 |
二、连接
客户端会发送如下参数过来
属性 | 描述 |
---|---|
VER | 协议版本,0x05 ,表示socks5 协议 |
CMD | 命令类型 |
RSV | 不用管 |
ATYP | 地址类型 |
DST.ADDR | 地址 |
DST.PORT | 端口 |
详细说明
o VER protocol version: X'05'
o CMD
o CONNECT X'01'
o BIND X'02'
o UDP ASSOCIATE X'03'
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o DST.ADDR desired destination address
o DST.PORT desired destination port in network octet
order
请求目的地地址解析说明
In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies
the type of address contained within the field:
o X'01'
the address is a version-4 IP address, with a length of 4 octets
# IPV4的地址读取4字节
o X'03'
the address field contains a fully-qualified domain name. The first
octet of the address field contains the number of octets of name that
follow, there is no terminating NUL octet.
# 域名方式,第一个字节是域名长度,后面读取按照长度进行读取,转换为域名字符串
o X'04'
the address is a version-6 IP address, with a length of 16 octets.
# IPV6则取16字节
服务端回复信息
o VER protocol version: X'05'
o REP Reply field:
o X'00' succeeded
o X'01' general SOCKS server failure
o X'02' connection not allowed by ruleset
o X'03' Network unreachable
o X'04' Host unreachable
o X'05' Connection refused
o X'06' TTL expired
o X'07' Command not supported
o X'08' Address type not supported
o X'09' to X'FF' unassigned
o RSV RESERVED
o ATYP address type of following address
o IP V4 address: X'01'
o DOMAINNAME: X'03'
o IP V6 address: X'04'
o BND.ADDR server bound address
o BND.PORT server bound port in network octet order
此处只只介绍
connect
类型的指令,其他类型的方式相似,按照官方文档进行实现即可,这里我们读取出来目的地的地址,然后创建一个tcp
的连接,把连接返回的dest.LocalAddr()
的地址和端口,这里直接填入IPV4
类型即可,填入BND.ADDR、BND.PORT
连接就完成了,后续就正常进行数据的交换了
三、数据正式传输
由于上面我们已经拿到了client和desc的sockt连接,只需要把他们后续发送的内容交换到他们各自通道上就行。
四、GO
语言代码
package main
import (
"encoding/binary"
"errors"
"fmt"
"io"
"net"
)
func main() {
server, err := net.Listen("tcp",":1080")
if err != nil {
panic(err)
}
for {
client, err := server.Accept()
if err != nil {
panic(err)
}
go process(client)
}
}
func process(client net.Conn) {
if err := Socks5Auth(client); err != nil {
fmt.Println("auth error:", err)
client.Close()
return
}
target, err := Socks5Connect(client)
if err != nil {
fmt.Println("connect error:", err)
client.Close()
return
}
Socks5Forward(client, target)
}
func Socks5Auth(client net.Conn) (err error) {
buf := make([]byte,256)
n, err := io.ReadFull(client,buf[:2])
if n!= 2 {
return errors.New("socks5 connect error")
}
ver, nmethods := int(buf[0]), int(buf[1])
if ver != 5 {
return errors.New("socks5 version error")
}
n, err = io.ReadFull(client, buf[:nmethods])
if n!= nmethods {
return errors.New("reading methods: " + err.Error())
}
// 用户名密码登陆
n, err = client.Write([]byte{0x05, 0x02})
n, err = io.ReadFull(client,buf[:2])
// 读取用户名
userNameLength := int(buf[1])
n, err = io.ReadFull(client,buf[:userNameLength])
userName := string(buf[:userNameLength])
n, err = io.ReadFull(client,buf[:1])
passwordLength := int(buf[0])
n, err = io.ReadFull(client,buf[:passwordLength])
password := string(buf[:passwordLength])
if userName != "hzh" && password != "123456" {
return errors.New("username or password err")
}
n, err = client.Write([]byte{0x05, 0x00})
// 该回复是不需要用户名和密码
// n, err = client.Write([]byte{0x05, 0x00})
if n != 2 || err != nil {
return errors.New("write rsp err:" + err.Error())
}
return nil
}
func Socks5Connect(client net.Conn) (net.Conn, error) {
buf := make([]byte,256)
n, err := io.ReadFull(client,buf[:4])
if n != 4 {
return nil, errors.New("read header: " + err.Error())
}
ver, cmd, _, atyp := buf[0], buf[1], buf[2], buf[3]
if ver != 5 || cmd != 1 {
return nil, errors.New("invalid ver/cmd")
}
addr := ""
switch atyp {
case 1:
n, err = io.ReadFull(client, buf[:4])
if n != 4 {
return nil, errors.New("invalid IPv4: " + err.Error())
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case 3:
n, err = io.ReadFull(client, buf[:1])
if n != 1 {
return nil, errors.New("invalid hostname: " + err.Error())
}
addrLen := int(buf[0])
n, err = io.ReadFull(client, buf[:addrLen])
if n != addrLen {
return nil, errors.New("invalid hostname: " + err.Error())
}
addr = string(buf[:addrLen])
case 4:
return nil, errors.New("IPv6: no supported yet")
default:
return nil, errors.New("invalid atyp")
}
n, err = io.ReadFull(client, buf[:2])
if n != 2 {
return nil, errors.New("read port: " + err.Error())
}
port := binary.BigEndian.Uint16(buf[:2])
destAddrPort := fmt.Sprintf("%s:%d", addr, port)
dest, err := net.Dial("tcp", destAddrPort)
fmt.Println(dest.LocalAddr())
if err != nil {
return nil, errors.New("dial dst: " + err.Error())
}
n, err = client.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0})
if err != nil {
dest.Close()
return nil, errors.New("write rsp: " + err.Error())
}
return dest, nil
}
func Socks5Forward(client, target net.Conn) {
defer client.Close()
defer target.Close()
forward := func(src, dest net.Conn,res chan int) {
io.Copy(src, dest)
res <- 1
}
res := make(chan int)
go forward(client, target, res)
go forward(target, client, res)
// 等待线程执行完毕
for i := 0; i < 2; i++ {
<- res
}
}
转载自:https://juejin.cn/post/7037351835748794382