go socks5实现
什么是SOCKS
SOCKS是一种网络传输协议,主要用于Client端与外网Server端之间通讯的中间传递。SOCKS是"SOCKETS"的缩写,意外这一切socks都可以通过代理。 SOCKS是一种代理协议,相比于常见的HTTP代理,SOCKS的代理更加的底层,传统的HTTP代理会修改HTTP头,SOCKS则不会它只是做了数据的中转。
SOCKS的最新版本为SOCKS5。
SOCKS5协议详解
SOCKS5的协议是基于二进制的,协议设计十分的简洁。
Client端和Server端第一次通讯的时候,Client端会想服务端发送版本认证信息,协议格式如下:
+----+----------+----------+
|VER | NMETHODS | METHODS |
+----+----------+----------+
| 1 | 1 | 1 to 255 |
+----+----------+----------+
- VER是SOCKS版本,目前版本是0x05;
- NMETHODS是METHODS部分的长度;
- METHODS是Client端支持的认证方式列表,每个方法占1字节。当前的定义是:
- 0x00 不需要认证
- 0x01 GSSAPI
- 0x02 用户名、密码认证
- 0x03 - 0x7F由IANA分配(保留)
- 0x80 - 0xFE为私人方法保留
- 0xFF 无可接受的方法
Server端从Client端提供的方法中选择一个并通过以下消息通知Client端:
+----+--------+
|VER | METHOD |
+----+--------+
| 1 | 1 |
+----+--------+
- VER是SOCKS版本,目前是0x05;
- METHOD是服务端选中的方法。如果返回0xFF表示没有一个认证方法被选中,Client端需要关闭连接。
SOCKS5请求协议格式:
+----+-----+-------+------+----------+----------+
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER是SOCKS版本,目前是0x05;
- CMD是SOCK的命令码
- 0x01表示CONNECT请求
- 0x02表示BIND请求
- 0x03表示UDP转发
- RSV 0x00,保留
- ATYP DST.ADDR类型
- 0x01 IPv4地址,DST.ADDR部分4字节长度
- 0x03 域名地址,DST.ADDR部分第一个字节为域名长度,剩余的内容为域名。
- 0x04 IPv6地址,16个字节长度。
- DST.ADDR 目的地址
- DST.PORT 网络字节序表示的目的端口
Server端按以下格式返回给Client端:
+----+-----+-------+------+----------+----------+
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
+----+-----+-------+------+----------+----------+
| 1 | 1 | X'00' | 1 | Variable | 2 |
+----+-----+-------+------+----------+----------+
- VER是SOCKS版本,目前是0x05;
- REP应答字段
- 0x00 表示成功
- 0x01 普通SOCKSServer端连接失败
- 0x02 现有规则不允许连接
- 0x03 网络不可达
- 0x04 主机不可达
- 0x05 连接被拒
- 0x06 TTL超时域名地址
- 0x07 不支持的命令
- 0x08 不支持的地址类型
- 0x09 - 0xFF未定义
- RSV 0x00,保留
- ATYP BND.ADDR类型
- 0x01 IPv4地址,DST.ADDR部分4字节长度
- 0x03 域名地址,DST.ADDR部分第一个字节为域名长度,剩余的内容为域名。
- 0x04 IPv6地址,16个字节长度。
- BND.ADDR Server端绑定的地址
- BND.PORT 网络字节序表示的Server端绑定的端口
用go实现socks5
package main
import (
"errors"
"fmt"
"io"
"log"
"net"
"strconv"
"strings"
)
const (
IPV4ADDR = uint8(1) //ipv4地址
DNADDR = uint8(3) //域名地址
IPV6ADDR = uint8(4) //地址
CONNECTCMD = uint8(1)
SUCCEEDED = uint8(0)
NETWORKUNREACHABLE = uint8(3)
HOSTUNREACHABLE = uint8(4)
CONNECTIONREFUSED = uint8(5)
COMMANDNOTSUPPORTED = uint8(7)
)
type Addr struct {
Dn string
IP net.IP
Port int
}
func (a Addr) Addr() string {
if 0 != len(a.IP) {
return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
}
return net.JoinHostPort(a.Dn, strconv.Itoa(a.Port))
}
type Command struct {
Version uint8
Command uint8
RemoteAddr *Addr
DestAddr *Addr
RealDestAddr *Addr
reader io.Reader
}
func auth(conn io.ReadWriter) error {
header := make([]byte,2)
_,err:= io.ReadFull(conn,header)
if err!=nil {
return err
}
//igonre check version
methods := make([]byte,int(header[1]))
_,err = io.ReadFull(conn,methods)
if err!=nil {
return err
}
_,err = conn.Write([]byte{uint8(5), uint8(0)}) // 返回协议5,不需要认证
return err
}
func readAddr(r io.Reader) (*Addr, error) {
d := &Addr{}
addrType := []byte{0}
if _, err := r.Read(addrType); err != nil {
return nil, err
}
switch addrType[0] {
case IPV4ADDR:
addr := make([]byte, 4)
if _, err := io.ReadFull(r, addr); err != nil {
return nil, err
}
d.IP = net.IP(addr)
case IPV6ADDR:
addr := make([]byte, 16)
if _, err := io.ReadFull(r, addr); err != nil {
return nil, err
}
d.IP = net.IP(addr)
case DNADDR:
if _, err := r.Read(addrType); err != nil {
return nil, err
}
addrLen := int(addrType[0])
DN := make([]byte, addrLen)
if _, err := io.ReadFull(r, DN); err != nil {
return nil, err
}
d.Dn = string(DN)
default:
return nil, errors.New("unkown addr type")
}
port := []byte{0, 0}
if _, err := io.ReadFull(r, port); err != nil {
return nil, err
}
d.Port = (int(port[0]) << 8) | int(port[1])
return d, nil
}
func request(conn io.ReadWriter) (*Command, error) {
header := []byte{0, 0, 0}
if _, err := io.ReadFull(conn, header); err != nil {
return nil, err
}
//igonre check version
dest, err := readAddr(conn)
if err != nil {
return nil, err
}
cmd := &Command{
Version: uint8(5),
Command: header[1],
DestAddr: dest,
reader: conn,
}
return cmd, nil
}
func replyMsg(w io.Writer, resp uint8, addr *Addr) error {
var addrType uint8
var addrBody []byte
var addrPort uint16
switch {
case addr == nil:
addrType = IPV4ADDR
addrBody = []byte{0, 0, 0, 0}
addrPort = 0
case addr.Dn != "":
addrType = DNADDR
addrBody = append([]byte{byte(len(addr.Dn))}, addr.Dn...)
addrPort = uint16(addr.Port)
case addr.IP.To4() != nil:
addrType = IPV4ADDR
addrBody = []byte(addr.IP.To4())
addrPort = uint16(addr.Port)
case addr.IP.To16() != nil:
addrType = IPV6ADDR
addrBody = []byte(addr.IP.To16())
addrPort = uint16(addr.Port)
default:
return errors.New("format address error")
}
bodyLen := len(addrBody)
msg := make([]byte, 6+bodyLen)
msg[0] = uint8(5)
msg[1] = resp
msg[2] = 0 // RSV
msg[3] = addrType
copy(msg[4:], addrBody)
msg[4+bodyLen] = byte(addrPort >> 8)
msg[4+bodyLen+1] = byte(addrPort & 0xff)
_, err := w.Write(msg)
return err
}
func handleSocks5(conn io.ReadWriteCloser) error {
if err := auth(conn);err !=nil {
return err
}
cmd,err := request(conn)
if err !=nil {
return err
}
fmt.Printf("%v",cmd.DestAddr)
if err := handleCmd( cmd, conn); err != nil {
return err
}
return nil
}
func handleCmd(cmd *Command, conn io.ReadWriteCloser) error {
dest := cmd.DestAddr
if dest.Dn != "" {
addr, err := net.ResolveIPAddr("ip", dest.Dn)
if err != nil {
if err := replyMsg(conn, HOSTUNREACHABLE, nil); err != nil {
return err
}
return err
}
dest.IP = addr.IP
}
cmd.RealDestAddr = cmd.DestAddr
switch cmd.Command {
case CONNECTCMD:
return handleConn(conn, cmd)
default:
if err := replyMsg(conn, COMMANDNOTSUPPORTED, nil); err != nil {
return err
}
return errors.New("Unsupported command")
}
}
func handleConn(conn io.ReadWriteCloser, req *Command) error {
target, err := net.Dial("tcp", req.RealDestAddr.Addr())
if err != nil {
msg := err.Error()
resp := HOSTUNREACHABLE
if strings.Contains(msg, "refused") {
resp = CONNECTIONREFUSED
} else if strings.Contains(msg, "network is unreachable") {
resp = NETWORKUNREACHABLE
}
if err := replyMsg(conn, resp, nil); err != nil {
return err
}
return errors.New(fmt.Sprintf("Connect to %v failed: %v", req.DestAddr, err))
}
defer target.Close()
local := target.LocalAddr().(*net.TCPAddr)
bind := Addr{IP: local.IP, Port: local.Port}
if err := replyMsg(conn, SUCCEEDED, &bind); err != nil {
return err
}
go io.Copy(target, req.reader)
io.Copy(conn, target)
return nil
}
func main() {
l,err := net.Listen("tcp",":8090")
if err !=nil {
log.Fatal(err)
}
for {
conn, err := l.Accept()
if err != nil {
log.Fatal(err)
}
handle := func(conn net.Conn) {
handleSocks5(conn)
}
go handle(conn)
}
}
$ go run main.go
&{ 180.101.49.12 443} ## 使用curl出现
$ export ALL_PROXY=socks5://127.0.0.1:8090 # 设置终端代理
$ curl https://www.baidu.com
<!DOCTYPE html>
...
上面的例子中我们,使用了linux的终端代理,这边隐藏了一个细节就是,终端代理实践上有socks5 client,所以我们并没有自己拼client相关的二进制协议。 linux的终端只支持http,https。像ssh协议这需要自己去实现client协议,有兴趣的同学可以自己去实现下,这不不再赘述。
总结
本小节介绍了socks5的协议,以及实现了socks代理server。socks5其实不仅仅支持tcp还有支持udp。总体来说是一个简洁但功能强大的协议。