完整实现流程
启动一个服务
ln, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("Server listen fail")
return
}
for {
sc, err := ln.Accept()
if err != nil {
fmt.Println("Server accept fail")
continue
}
go handleServer(sc)
}
解析请求流程
确认版本和认证方式
服务端会接收到三个字节依次表示 VER、NMETHODS、METHODS,在得到连接请求时,需要响应客户端 VER 和采用的 METHOD,此处响应 5、0 表示无需认证
buf := make([]byte, 1024)
// read VER, NMETHODS, METHODS
if _, err := io.ReadFull(sc, buf[:2]); err != nil {
return
}
nMethods := buf[1]
if _, err := io.ReadFull(sc, buf[:nMethods]); err != nil {
return
}
// write VER METHOD
if _, err := sc.Write([]byte{5, 0}); err != nil {
return
}
建立连接
客户端建立连接时会带上 VER、CMD、RSV、ATYP、DST.ADDR、DST.PORT(详见 socks5 协议原理),服务端需要响应客户端连接成功
// read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err := io.ReadFull(sc, buf[:3]); err != nil {
return
}
// reply success
_, _ = sc.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
转发请求
首先需要从请求里面解析出 remoteAddr,
b := make(byte[], 1024)
_, err := io.ReadFull(r, b[:1])
if err != nil {
return "", err
}
host, port := "", ""
switch b[0] {
case 3:
_, err = io.ReadFull(r, b[1:2])
if err != nil {
return "", err
}
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
host = string(b[2 : 2+int(b[1])])
port = strconv.Itoa((int(b[2+int(b[1])]) << 8) | int(b[2+int(b[1])+1]))
case 1:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
host = net.IP(b[1 : 1+net.IPv4len]).String()
port = strconv.Itoa((int(b[1+net.IPv4len]) << 8) | int(b[1+net.IPv4len+1]))
case 4:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
host = net.IP(b[1 : 1+net.IPv6len]).String()
port = strconv.Itoa((int(b[1+net.IPv6len]) << 8) | int(b[1+net.IPv6len+1]))
default:
return "", errors.New("invalid aType")
}
address := net.JoinHostPort(host, port)
return address, nil
然后跟 remoteAddr 建立连接
rc, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println("server dial fail")
return
}
defer rc.Close()
最后中转客户端和远端 remote 之间交互的字节流
go func() {
io.Copy(right, left)
}()
io.Copy(left, right)
完整代码
package main
import (
"errors"
"fmt"
"io"
"net"
"strconv"
)
func main() {
ln, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("Server listen fail")
return
}
for {
sc, err := ln.Accept()
if err != nil {
fmt.Println("Server accept fail")
continue
}
go handleServer(sc)
}
}
func handleServer(sc net.Conn) {
defer sc.Close()
buf := make([]byte, 1024)
// read VER, NMETHODS, METHODS
if _, err := io.ReadFull(sc, buf[:2]); err != nil {
return
}
nmethods := buf[1]
if _, err := io.ReadFull(sc, buf[:nmethods]); err != nil {
return
}
// write VER METHOD
if _, err := sc.Write([]byte{5, 0}); err != nil {
return
}
// read VER CMD RSV ATYP DST.ADDR DST.PORT
if _, err := io.ReadFull(sc, buf[:3]); err != nil {
return
}
_, _ = sc.Write([]byte{5, 0, 0, 1, 0, 0, 0, 0, 0, 0})
b := make([]byte, 1024)
addr, err := ReadAddr(sc, b)
if err != nil {
return
}
rc, err := net.Dial("tcp", addr)
if err != nil {
fmt.Println("server dial fail")
return
}
defer rc.Close()
if err = relay(sc, rc); err != nil {
fmt.Printf("relay error: %v", err)
return
}
}
func ReadAddr(r io.Reader, b []byte) (string, error) {
_, err := io.ReadFull(r, b[:1])
if err != nil {
return "", err
}
host, port := "", ""
switch b[0] {
case 3:
_, err = io.ReadFull(r, b[1:2])
if err != nil {
return "", err
}
_, err = io.ReadFull(r, b[2:2+int(b[1])+2])
host = string(b[2 : 2+int(b[1])])
port = strconv.Itoa((int(b[2+int(b[1])]) << 8) | int(b[2+int(b[1])+1]))
case 1:
_, err = io.ReadFull(r, b[1:1+net.IPv4len+2])
host = net.IP(b[1 : 1+net.IPv4len]).String()
port = strconv.Itoa((int(b[1+net.IPv4len]) << 8) | int(b[1+net.IPv4len+1]))
case 4:
_, err = io.ReadFull(r, b[1:1+net.IPv6len+2])
host = net.IP(b[1 : 1+net.IPv6len]).String()
port = strconv.Itoa((int(b[1+net.IPv6len]) << 8) | int(b[1+net.IPv6len+1]))
default:
return "", errors.New("invalid aType")
}
address := net.JoinHostPort(host, port)
return address, nil
}
func relay(left, right net.Conn) error {
go func() {
io.Copy(right, left)
}()
io.Copy(left, right)
return nil
}
上述代码运行成功之后,配合浏览器的插件 SwitchyOmega 即可实现代理