mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-07 09:41:07 +08:00

现在整个程序均通过了go test, main 也可以正常运行了。 Relay_UDP 函数添加流量计数; 发现之前 Relay函数的流量计数 在main.go里参数传反了,导致实际上计数的是上传而不是下载,已修复 对fullcone的情况做了特别考量。MsgConn的 Close函数在fullcone时不能随便被调用。 因此我添加了一个 CloseConnWithRaddr(raddr Addr) error 方法,以及 Fullcone() bool 方法 在utils包的init部分使用 rand 随机种子
339 lines
8.5 KiB
Go
339 lines
8.5 KiB
Go
package socks5
|
||
|
||
import (
|
||
"bytes"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
"net/url"
|
||
"time"
|
||
|
||
"github.com/hahahrfool/v2ray_simple/netLayer"
|
||
"github.com/hahahrfool/v2ray_simple/utils"
|
||
|
||
"github.com/hahahrfool/v2ray_simple/proxy"
|
||
)
|
||
|
||
func init() {
|
||
proxy.RegisterServer(Name, &ServerCreator{})
|
||
}
|
||
|
||
// TODO: Support Auth
|
||
type Server struct {
|
||
proxy.ProxyCommonStruct
|
||
//user string
|
||
//password string
|
||
}
|
||
|
||
type ServerCreator struct{}
|
||
|
||
func (_ ServerCreator) NewServerFromURL(u *url.URL) (proxy.Server, error) {
|
||
s := &Server{}
|
||
return s, nil
|
||
}
|
||
|
||
func (_ ServerCreator) NewServer(dc *proxy.ListenConf) (proxy.Server, error) {
|
||
s := &Server{}
|
||
return s, nil
|
||
}
|
||
|
||
func (s *Server) Name() string { return Name }
|
||
|
||
//English: https://www.ietf.org/rfc/rfc1928.txt
|
||
|
||
//中文: https://aber.sh/articles/Socks5/
|
||
// 参考 https://studygolang.com/articles/31404
|
||
|
||
// 处理tcp收到的请求. 注意, udp associate后的 udp请求并不通过此函数处理, 而是由 UDPConn 处理
|
||
func (s *Server) Handshake(underlay net.Conn) (result io.ReadWriteCloser, udpChannel netLayer.MsgConn, targetAddr netLayer.Addr, returnErr error) {
|
||
// Set handshake timeout 4 seconds
|
||
if err := underlay.SetReadDeadline(time.Now().Add(time.Second * 4)); err != nil {
|
||
returnErr = err
|
||
return
|
||
}
|
||
defer underlay.SetReadDeadline(time.Time{})
|
||
|
||
buf := utils.GetMTU()
|
||
defer utils.PutBytes(buf)
|
||
|
||
// Read hello message
|
||
// 一般握手包发来的是 [5 1 0]
|
||
n, err := underlay.Read(buf)
|
||
if err != nil || n == 0 {
|
||
returnErr = fmt.Errorf("failed to read hello: %w", err)
|
||
return
|
||
}
|
||
version := buf[0]
|
||
if version != Version5 {
|
||
returnErr = fmt.Errorf("unsupported socks version %v", version)
|
||
return
|
||
}
|
||
|
||
// Write hello response, [5 0]
|
||
// TODO: Support Auth
|
||
_, err = underlay.Write([]byte{Version5, AuthNone})
|
||
if err != nil {
|
||
returnErr = fmt.Errorf("failed to write hello response: %w", err)
|
||
return
|
||
}
|
||
|
||
// Read command message,
|
||
n, err = underlay.Read(buf)
|
||
if err != nil || n < 7 { // Shortest length is 7
|
||
returnErr = fmt.Errorf("read socks5 failed, msgTooShort: %w", err)
|
||
return
|
||
}
|
||
|
||
// 一般可以为 5 1 0 3 n,3表示域名,n是域名长度,然后域名很可能是 119 119 119 46 开头,表示 www.
|
||
// 比如百度就是 [5 1 0 3 13 119 119 119 46 98]
|
||
|
||
cmd := buf[1]
|
||
if cmd == CmdBind {
|
||
returnErr = fmt.Errorf("unsuppoted command %v", cmd)
|
||
return
|
||
}
|
||
|
||
l := 2
|
||
off := 4
|
||
var theIP net.IP
|
||
|
||
switch buf[3] {
|
||
case ATypIP4:
|
||
l += net.IPv4len
|
||
theIP = make(net.IP, net.IPv4len)
|
||
case ATypIP6:
|
||
l += net.IPv6len
|
||
theIP = make(net.IP, net.IPv6len)
|
||
case ATypDomain:
|
||
l += int(buf[4])
|
||
off = 5
|
||
default:
|
||
returnErr = fmt.Errorf("unknown address type %v", buf[3])
|
||
return
|
||
}
|
||
|
||
if len(buf[off:]) < l {
|
||
returnErr = errors.New("short command request")
|
||
return
|
||
}
|
||
|
||
var theName string
|
||
|
||
if theIP != nil {
|
||
copy(theIP, buf[off:])
|
||
} else {
|
||
theName = string(buf[off : off+l-2])
|
||
}
|
||
var thePort int
|
||
thePort = int(buf[off+l-2])<<8 | int(buf[off+l-1])
|
||
|
||
//根据 socks5标准,“使用UDP ASSOCIATE时,客户端的请求包中(DST.ADDR, DST.PORT)不再是目标的地址,而是客户端指定本身用于发送UDP数据包的地址和端口”
|
||
//然后服务器会传回专门适用于客户端的 一个 服务器的 udp的ip和地址;然后之后客户端再专门 向udp地址发送连接,此tcp连接就已经没用。
|
||
//总之,UDP Associate方法并不是 UDP over TCP,完全不同,而且过程中握手用tcp,传输用udp,使用到了两个连接。
|
||
|
||
//不过一般作为NAT内网中的客户端是无法知道自己实际呈现给服务端的udp地址的, 所以没什么太大用
|
||
// 但是, 如果不指定的话,udp associate 就会认为未来一切发往我们新生成的端口的连接都是属于该客户端, 显然有风险
|
||
// 更不用说这样 的 udp associate 会重复使用很多 随机udp端口,特征很明显。
|
||
// 总之 udp associate 只能用于内网环境。
|
||
|
||
if cmd == CmdUDPAssociate {
|
||
|
||
//这里我们serverAddr直接返回0.0.0.0即可,也实在想不到谁会返回一个另一个ip地址出来。
|
||
|
||
//随机生成一个端口专门用于处理该客户端。这是我的想法。
|
||
|
||
bindPort := netLayer.RandPort()
|
||
|
||
udpPreparedAddr := &net.UDPAddr{
|
||
IP: []byte{0, 0, 0, 0},
|
||
Port: bindPort,
|
||
}
|
||
|
||
udpRC, err := net.ListenUDP("udp", udpPreparedAddr)
|
||
if err != nil {
|
||
returnErr = errors.New("UDPAssociate: unable to listen udp")
|
||
return
|
||
}
|
||
|
||
//ver(5), rep(0,表示成功), rsv(0), atyp(1, 即ipv4), BND.ADDR(4字节的ipv4) , BND.PORT(2字节)
|
||
reply := []byte{Version5, 0x00, 0x00, 0x01, 0, 0, 0, 0, byte(int16(bindPort) >> 8), byte(int16(bindPort) << 8 >> 8)}
|
||
|
||
// Write command response
|
||
_, err = underlay.Write(reply)
|
||
if err != nil {
|
||
returnErr = fmt.Errorf("failed to write command response: %w", err)
|
||
return
|
||
}
|
||
|
||
clientFutureAddr := netLayer.Addr{
|
||
IP: theIP,
|
||
Name: theName,
|
||
Port: thePort,
|
||
Network: "udp",
|
||
}
|
||
|
||
//这里为了解析域名, 就用了 netLayer.Addr 作为中介的方式
|
||
uc := &UDPConn{
|
||
clientSupposedAddr: clientFutureAddr.ToUDPAddr(),
|
||
UDPConn: udpRC,
|
||
}
|
||
return nil, uc, clientFutureAddr, nil
|
||
|
||
} else {
|
||
|
||
// 解读如下:
|
||
//ver(5), rep(0,表示成功), rsv(0), atyp(1, 即ipv4), BND.ADDR (ipv4(0,0,0,0)), BND.PORT(0, 2字节)
|
||
reply := []byte{Version5, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||
|
||
//这个 BND.ADDR和port 按理说不应该传0的,不过如果只作为本地tcp代理的话应该不影响
|
||
|
||
_, err = underlay.Write(reply)
|
||
if err != nil {
|
||
returnErr = fmt.Errorf("failed to write command response: %w", err)
|
||
return
|
||
}
|
||
|
||
//theName有可能是ip的形式,比如浏览器一般不会自己dns,把一切ip和域名都当作域名传入socks5代理
|
||
|
||
if ip := net.ParseIP(theName); ip != nil {
|
||
theIP = ip
|
||
}
|
||
|
||
targetAddr = netLayer.Addr{
|
||
IP: theIP,
|
||
Name: theName,
|
||
Port: thePort,
|
||
}
|
||
|
||
return underlay, nil, targetAddr, nil
|
||
}
|
||
|
||
}
|
||
|
||
//用于socks5服务端的 udp连接, 实现 netLayer.MsgConn
|
||
type UDPConn struct {
|
||
*net.UDPConn
|
||
clientSupposedAddr *net.UDPAddr //客户端指定的客户端自己未来将使用的公网UDP的Addr
|
||
}
|
||
|
||
func (u *UDPConn) CloseConnWithRaddr(raddr netLayer.Addr) error {
|
||
return u.Close()
|
||
}
|
||
|
||
func (u *UDPConn) Fullcone() bool {
|
||
return true
|
||
}
|
||
|
||
//将远程地址发来的响应 传给客户端
|
||
func (u *UDPConn) WriteTo(bs []byte, raddr netLayer.Addr) error {
|
||
|
||
buf := &bytes.Buffer{}
|
||
buf.WriteByte(0) //rsv
|
||
buf.WriteByte(0) //rsv
|
||
buf.WriteByte(0) //frag
|
||
|
||
var atyp byte = ATypIP4
|
||
if len(raddr.IP) > 4 {
|
||
atyp = ATypIP6
|
||
}
|
||
buf.WriteByte(atyp)
|
||
|
||
buf.Write(raddr.IP)
|
||
buf.WriteByte(byte(int16(raddr.Port) >> 8))
|
||
buf.WriteByte(byte(int16(raddr.Port) << 8 >> 8))
|
||
buf.Write(bs)
|
||
|
||
//必须要指明raddr
|
||
_, err := u.UDPConn.WriteToUDP(buf.Bytes(), u.clientSupposedAddr)
|
||
|
||
return err
|
||
|
||
}
|
||
|
||
//从 客户端读取 udp请求
|
||
func (u *UDPConn) ReadFrom() ([]byte, netLayer.Addr, error) {
|
||
|
||
var clientSupposedAddrIsNothing bool
|
||
if len(u.clientSupposedAddr.IP) < 3 || u.clientSupposedAddr.IP.IsUnspecified() {
|
||
clientSupposedAddrIsNothing = true
|
||
}
|
||
|
||
bs := utils.GetPacket()
|
||
|
||
n, addr, err := u.UDPConn.ReadFromUDP(bs)
|
||
if err != nil {
|
||
|
||
if n <= 0 {
|
||
return nil, netLayer.Addr{}, err
|
||
}
|
||
return bs[:n], netLayer.NewAddrFromUDPAddr(addr), err
|
||
}
|
||
|
||
if n < 6 {
|
||
|
||
return nil, netLayer.Addr{}, utils.ErrInErr{ErrDesc: "socks5 UDPConn short read", Data: n}
|
||
}
|
||
|
||
if !clientSupposedAddrIsNothing {
|
||
|
||
if !addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port {
|
||
|
||
//just random attack message.
|
||
return nil, netLayer.Addr{}, errors.New("socks5 UDPConn random attack message")
|
||
|
||
}
|
||
}
|
||
|
||
atyp := bs[3]
|
||
|
||
l := 2 //supposed Minimum Remain Data Lenth
|
||
off := 4 //offset from which the addr data really starts
|
||
|
||
var theIP net.IP
|
||
switch atyp {
|
||
case ATypIP4:
|
||
l += net.IPv4len
|
||
theIP = make(net.IP, net.IPv4len)
|
||
case ATypIP6:
|
||
l += net.IPv6len
|
||
theIP = make(net.IP, net.IPv6len)
|
||
case ATypDomain:
|
||
l += int(bs[4])
|
||
off = 5
|
||
default:
|
||
|
||
return nil, netLayer.Addr{}, utils.ErrInErr{ErrDesc: "socks5 read UDPConn unknown atype", Data: atyp}
|
||
|
||
}
|
||
|
||
if len(bs[off:]) < l {
|
||
|
||
return nil, netLayer.Addr{}, utils.ErrInErr{ErrDesc: "socks5 UDPConn short command request", Data: atyp}
|
||
|
||
}
|
||
|
||
var theName string
|
||
|
||
if theIP != nil {
|
||
copy(theIP, bs[off:])
|
||
} else {
|
||
theName = string(bs[off : off+l-2])
|
||
}
|
||
var thePort int
|
||
thePort = int(bs[off+l-2])<<8 | int(bs[off+l-1])
|
||
|
||
newStart := off + l
|
||
|
||
if clientSupposedAddrIsNothing {
|
||
clientSupposedAddrIsNothing = false
|
||
u.clientSupposedAddr = addr
|
||
}
|
||
return bs[newStart:n], netLayer.Addr{
|
||
IP: theIP,
|
||
Name: theName,
|
||
Port: thePort,
|
||
Network: "udp",
|
||
}, nil
|
||
}
|