mirror of
https://github.com/e1732a364fed/v2ray_simple.git
synced 2025-10-28 02:51:43 +08:00
防止udp转发时被裸奔;修缮socks udp associate
修订代码,文档,示例 添加 NumErr 结构; 从 handshakeInserver_and_passToOutClient 函数 分离出一个 dialClient 函数。 在socks5包中添加 client.go 文件,以及三个udp相关的客户端请求udp函数 之前的udp associate代码被证明是有很多bug的,现在被我一一修复,并通过了 udp_test.go的测试。
This commit is contained in:
7
Makefile
7
Makefile
@@ -70,11 +70,8 @@ win10:
|
||||
|
||||
|
||||
clean:
|
||||
rm -f $(linuxAmdFn)
|
||||
rm -f $(linuxArmFn)
|
||||
rm -f ${winFn}.exe
|
||||
rm -f $(macFn)
|
||||
rm -f $(macM1Fn)
|
||||
rm -f verysimple
|
||||
rm -f verysimple.exe
|
||||
rm -f $(linuxAmdFn).tgz
|
||||
rm -f $(linuxArmFn).tgz
|
||||
rm -f ${winFn}.tgz
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
"github.com/hahahrfool/v2ray_simple/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
mmdbDownloadLink = "https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
|
||||
)
|
||||
|
||||
var (
|
||||
cmdPrintSupportedProtocols bool
|
||||
cmdGenerateUUID bool
|
||||
|
||||
@@ -12,6 +12,7 @@ advancedLayer = "grpc"
|
||||
path = "ohmygod_verysimple_is_very_simple" #正常来说不宜前面再加斜杠,不过我也没试过,也许加了也能用(两端都加的情况下)
|
||||
|
||||
# 如需使用 Nginx、Caddy 等软件进行分流,设置的分流路径应为 /${path}/Tun
|
||||
# 比如当前示例的情况下,Nginx的配置就是 /ohmygod_verysimple_is_very_simple/Tun
|
||||
|
||||
[[dial]]
|
||||
protocol = "direct"
|
||||
|
||||
@@ -14,4 +14,4 @@ insecure = true
|
||||
#utls = true # 如果你要用v2ray或者xray做服务端,则要关闭utls,它们不够强大所以不行;如果你用verysimple做服务端,则可以打开utls,享受chrome指纹伪装。
|
||||
advancedLayer = "ws"
|
||||
path = "/ohmygod_verysimple_is_very_simple" #为了防探测这里越长越随机越好
|
||||
#extra = { ws_earlydata = true } # 是否开启early data
|
||||
#extra = { ws_earlydata = true } # 是否开启early data, 注意本作这里要开启的话,就要两端都开启。
|
||||
|
||||
140
main.go
140
main.go
@@ -33,10 +33,6 @@ const (
|
||||
v2rayCompatibleMode
|
||||
)
|
||||
|
||||
const (
|
||||
mmdbDownloadLink = "https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
|
||||
)
|
||||
|
||||
var (
|
||||
configFileName string
|
||||
|
||||
@@ -62,9 +58,6 @@ var (
|
||||
|
||||
routePolicy *netLayer.RoutePolicy
|
||||
mainFallback *httpLayer.ClassicFallback
|
||||
|
||||
//isServerEnd bool //这个是代码里推断的,不一定准确;不过目前仅被用于tls lazy encrypt,所以不是很重要
|
||||
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -102,10 +95,10 @@ func main() {
|
||||
utils.LogLevel = cmdLL
|
||||
}
|
||||
|
||||
if cmdUseReadv == false && netLayer.UseReadv == true {
|
||||
if cmdUseReadv != netLayer.UseReadv {
|
||||
//配置文件配置了readv, 但是因为 命令行给出的值优先, 所以要设回
|
||||
|
||||
netLayer.UseReadv = false
|
||||
netLayer.UseReadv = cmdUseReadv
|
||||
}
|
||||
|
||||
fmt.Println("Log Level:", utils.LogLevel)
|
||||
@@ -213,8 +206,6 @@ func main() {
|
||||
defaultOutClient = allClients[0]
|
||||
}
|
||||
|
||||
//isServerEnd = defaultOutClient.Name() == "direct"
|
||||
|
||||
// 后台运行主代码,而main函数只监听中断信号
|
||||
// TODO: 未来main函数可以推出 交互模式,等未来推出动态增删用户、查询流量等功能时就有用;
|
||||
// 或可用于交互生成自己想要的配置
|
||||
@@ -299,7 +290,7 @@ type incomingInserverConnState struct {
|
||||
// 在多路复用的情况下, 可能产生多个 IncomingInserverConnState,
|
||||
// 共用一个 baseLocalConn, 但是 wrappedConn 各不相同。
|
||||
|
||||
//这里说的多路复用基本指的就是grpc; 如果是 vless内嵌 mux.cool 的话不属于这种情况.
|
||||
//这里说的多路复用基本指的就是grpc/quic; 如果是 vless内嵌 mux.cool 的话不属于这种情况.
|
||||
|
||||
// 要区分 多路复用的包装 是在 vless等代理的握手验证 的外部 还是 内部
|
||||
|
||||
@@ -319,7 +310,7 @@ type incomingInserverConnState struct {
|
||||
|
||||
isTlsLazyServerEnd bool
|
||||
|
||||
shouldCloseBaseConnWhenComplete bool
|
||||
shouldCloseBaseConnAfterCopyComplete bool
|
||||
|
||||
routedToDirect bool
|
||||
}
|
||||
@@ -699,9 +690,10 @@ afterLocalServerHandshake:
|
||||
//这一段代码是去判断是否要在转发结束后自动关闭连接
|
||||
//如果目标是udp则要分情况讨论
|
||||
//
|
||||
// 这里 因为 vless v1 的 CRUMFURS 信道 会对 wrappedConn 出现keep alive ,
|
||||
// 这里 因为 vless v1 的 CRUMFURS 信道 会对 wrappedConn 进行 keep alive ,
|
||||
// 而且具体的传递信息的部分并不是在main函数中处理,而是自己的go routine,所以不能直接关闭 wrappedConn
|
||||
// 所以要分情况进行 defer wrappedConn.Close()。
|
||||
// 然后这里是设置 iics.shouldCloseBaseConnWhenComplete, 然后在dialClient函数里再实际 defer
|
||||
|
||||
if targetAddr.IsUDP() {
|
||||
|
||||
@@ -719,7 +711,7 @@ afterLocalServerHandshake:
|
||||
} else {
|
||||
//如果不是CRUMFURS命令,那就是普通的针对某udp地址的连接,见下文 uniExtractor 的使用
|
||||
//defer wrappedConn.Close()
|
||||
iics.shouldCloseBaseConnWhenComplete = true
|
||||
iics.shouldCloseBaseConnAfterCopyComplete = true
|
||||
}
|
||||
|
||||
case "socks5":
|
||||
@@ -729,12 +721,11 @@ afterLocalServerHandshake:
|
||||
// 因为socks5的 UDP Associate 办法是较为特殊的,不使用现有tcp而是新建立udp,所以此时该tcp连接已经没用了
|
||||
// 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate,并不包含实际地址信息
|
||||
|
||||
//defer wrappedConn.Close()
|
||||
wrappedConn.Close()
|
||||
//但是根据socks5标准,这个tcp链接同样是 keep alive的,否则客户端就会认为服务端挂掉了.
|
||||
|
||||
default:
|
||||
//defer wrappedConn.Close()
|
||||
iics.shouldCloseBaseConnWhenComplete = true
|
||||
iics.shouldCloseBaseConnAfterCopyComplete = true
|
||||
|
||||
}
|
||||
} else {
|
||||
@@ -743,7 +734,7 @@ afterLocalServerHandshake:
|
||||
//lazy_encrypt情况比较特殊
|
||||
// 如果不是lazy的情况的话,转发结束后,要自动关闭
|
||||
//defer wrappedConn.Close()
|
||||
iics.shouldCloseBaseConnWhenComplete = true
|
||||
iics.shouldCloseBaseConnAfterCopyComplete = true
|
||||
|
||||
}
|
||||
|
||||
@@ -794,44 +785,51 @@ afterLocalServerHandshake:
|
||||
// 此时socks5包已经帮我们dial好了一个udp连接,即wlc,但是还未读取到客户端想要访问的东西
|
||||
udpConn := wlc.(*socks5.UDPConn)
|
||||
|
||||
if client.Name() == "vless" {
|
||||
vc := client.(*vless.Client)
|
||||
switch vc.Version() {
|
||||
case 0:
|
||||
//基本情况与v1类似,但是不能用分离信道,只能接到一个udp请求就拨一个号
|
||||
|
||||
case 1:
|
||||
|
||||
// 将 outClient 视为 UDP_Putter ,就可以转发udp信息了
|
||||
// direct 也实现了 UDP_Putter (通过 UDP_Pipe和 RelayUDP_to_Direct函数), 所以目前 socks5直接转发udp到direct 的功能 已经实现。
|
||||
|
||||
// 我在 vless v1 的client 的 UserConn 中实现了 UDP_Putter, vless 的 client的 新连接的Handshake过程会在 UDP_Putter.WriteUDPRequest 被调用 时发生
|
||||
|
||||
if putter := client.(netLayer.UDP_Putter); putter != nil {
|
||||
|
||||
//UDP_Putter 不使用传统的Handshake过程,因为Handshake是用于第一次数据,然后后面接着的双向传输都不再需要额外信息;而 UDP_Putter 每一次数据传输都是需要传输 目标地址的,所以每一次都需要一些额外数据,这就是我们 UDP_Putter 接口去解决的事情。
|
||||
|
||||
//因为UDP Associate后,就会保证以后的向 wlc 的 所有请求数据都是udp请求,所以可以在这里直接循环转发了。
|
||||
|
||||
go udpConn.StartPushResponse(putter)
|
||||
udpConn.StartReadRequest(putter)
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dialFunc := func(targetAddr *netLayer.Addr) (io.ReadWriter, error) {
|
||||
return dialClient(incomingInserverConnState{}, targetAddr, client, false, nil, true)
|
||||
}
|
||||
|
||||
// 将 outClient 视为 UDP_Putter ,就可以转发udp信息了
|
||||
|
||||
//direct 和 vless 都实现了 UDP_Putter.
|
||||
|
||||
// direct 实现了 UDP_Putter (通过 UDP_Pipe和 RelayUDP_to_Direct函数), 所以目前 socks5直接转发udp到direct 的功能 已经实现。
|
||||
|
||||
// 我在 vless 的client 的 UserConn 中实现了 UDP_Putter, 新连接的Handshake过程会在 dialFunc 被调用 时发生
|
||||
|
||||
if putter := client.(netLayer.UDP_Putter); putter != nil {
|
||||
|
||||
//UDP_Putter 不使用传统的Handshake过程,因为Handshake是用于第一次数据,然后后面接着的双向传输都不再需要额外信息;而 UDP_Putter 每一次数据传输都是需要传输 目标地址的,所以每一次都需要一些额外数据,这就是我们 UDP_Putter 接口去解决的事情。
|
||||
|
||||
//因为UDP Associate后,就会保证以后的向 wlc 的 所有请求数据都是udp请求,所以可以在这里直接循环转发了。
|
||||
|
||||
go udpConn.StartPushResponse(putter)
|
||||
|
||||
udpConn.StartReadRequest(putter, dialFunc)
|
||||
|
||||
} else {
|
||||
if utils.CanLogErr() {
|
||||
log.Println("socks5 server -> client for udp, but client didn't implement netLayer.UDP_Putter", client.Name())
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dialClient(iics, targetAddr, client, isTlsLazy_clientEnd, wlc)
|
||||
dialClient(iics, targetAddr, client, isTlsLazy_clientEnd, wlc, false)
|
||||
}
|
||||
|
||||
func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, client proxy.Client, isTlsLazy_clientEnd bool, wlc io.ReadWriter) {
|
||||
// dialClient 对实际client进行拨号,处理传输层, tls层, 高级层等所有层级后,进行代理层握手,
|
||||
// 然后 进行实际转发(Copy)。
|
||||
// targetAddr为用户所请求的地址。
|
||||
//client为真实要拨号的client,可能会与iics里的defaultClient不同。以client为准。
|
||||
// wlc为调用者所提供的 此请求的 来源 链接。wlc主要用于 Copy阶段.
|
||||
// noCopy时为了让其它调用者自行处理 转发 时使用。
|
||||
func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, client proxy.Client, isTlsLazy_clientEnd bool, wlc io.ReadWriter, noCopy bool) (io.ReadWriter, error) {
|
||||
|
||||
if iics.shouldCloseBaseConnWhenComplete {
|
||||
if iics.shouldCloseBaseConnAfterCopyComplete && !noCopy {
|
||||
defer iics.wrappedConn.Close()
|
||||
}
|
||||
|
||||
@@ -849,7 +847,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien
|
||||
log.Println("request isn't the appointed domain", targetAddr, uniqueTestDomain)
|
||||
|
||||
}
|
||||
return
|
||||
return nil, &utils.NumErr{N: 1, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
if utils.CanLogInfo() {
|
||||
@@ -892,7 +890,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien
|
||||
log.Println("failed in dial", realTargetAddr.String(), ", Reason: ", err)
|
||||
|
||||
}
|
||||
return
|
||||
return nil, &utils.NumErr{N: 2, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
//log.Println("dial real addr ok", realTargetAddr)
|
||||
@@ -903,18 +901,18 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien
|
||||
|
||||
if isTlsLazy_clientEnd {
|
||||
|
||||
if tls_lazy_secure {
|
||||
if tls_lazy_secure && wlc != nil {
|
||||
// 如果使用secure办法,则我们每次不能先拨号,而是要detect用户的首包后再拨号
|
||||
// 这种情况只需要客户端操作, 此时我们wrc直接传入原始的 刚拨号好的 tcp连接,即 clientConn
|
||||
|
||||
// 而且为了避免黑客攻击或探测,我们要使用uuid作为特殊指令,此时需要 UserServer和 UserClient
|
||||
|
||||
if uc := client.(proxy.UserClient); uc != nil {
|
||||
tryRawCopy(true, uc, nil, targetAddr, clientConn, wlc, nil, true, nil)
|
||||
tryTlsLazyRawCopy(true, uc, nil, targetAddr, clientConn, wlc, nil, true, nil)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
return nil, &utils.NumErr{N: 3, Prefix: "dialClient err, "}
|
||||
|
||||
} else {
|
||||
clientEndRemoteClientTlsRawReadRecorder = tlsLayer.NewRecorder()
|
||||
@@ -927,7 +925,7 @@ func dialClient(iics incomingInserverConnState, targetAddr *netLayer.Addr, clien
|
||||
tlsConn, err := client.GetTLS_Client().Handshake(clientConn)
|
||||
if err != nil {
|
||||
log.Println("failed in handshake outClient tls", targetAddr.String(), ", Reason: ", err)
|
||||
return
|
||||
return nil, &utils.NumErr{N: 4, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
clientConn = tlsConn
|
||||
@@ -948,8 +946,11 @@ advLayerStep:
|
||||
log.Println("grpc.ClientHandshake failed,", err)
|
||||
|
||||
}
|
||||
iics.baseLocalConn.Close()
|
||||
return
|
||||
if iics.baseLocalConn != nil {
|
||||
iics.baseLocalConn.Close()
|
||||
|
||||
}
|
||||
return nil, &utils.NumErr{N: 5, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -960,8 +961,10 @@ advLayerStep:
|
||||
log.Println("grpc.DialNewSubConn failed,", err)
|
||||
|
||||
}
|
||||
iics.baseLocalConn.Close()
|
||||
return
|
||||
if iics.baseLocalConn != nil {
|
||||
iics.baseLocalConn.Close()
|
||||
}
|
||||
return nil, &utils.NumErr{N: 6, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
case "ws":
|
||||
@@ -969,7 +972,7 @@ advLayerStep:
|
||||
|
||||
var ed []byte
|
||||
|
||||
if wsClient.UseEarlyData {
|
||||
if wsClient.UseEarlyData && wlc != nil {
|
||||
//若配置了 MaxEarlyDataLen,则我们先读一段;
|
||||
edBuf := utils.GetPacket()
|
||||
edBuf = edBuf[:ws.MaxEarlyDataLen]
|
||||
@@ -978,7 +981,7 @@ advLayerStep:
|
||||
if utils.CanLogErr() {
|
||||
log.Println("err when reading ws early data", e)
|
||||
}
|
||||
return
|
||||
return nil, &utils.NumErr{N: 7, Prefix: "dialClient err, "}
|
||||
}
|
||||
ed = edBuf[:n]
|
||||
//log.Println("will send early data", n, ed)
|
||||
@@ -1004,7 +1007,7 @@ advLayerStep:
|
||||
log.Println("failed in handshake ws to", targetAddr.String(), ", Reason: ", err)
|
||||
|
||||
}
|
||||
return
|
||||
return nil, &utils.NumErr{N: 8, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
clientConn = wc
|
||||
@@ -1019,12 +1022,16 @@ advLayerStep:
|
||||
log.Println("failed in handshake to", targetAddr.String(), ", Reason: ", err)
|
||||
|
||||
}
|
||||
return
|
||||
return nil, &utils.NumErr{N: 9, Prefix: "dialClient err, "}
|
||||
}
|
||||
//log.Println("all handshake finished")
|
||||
|
||||
////////////////////////////// 实际转发阶段 /////////////////////////////////////
|
||||
|
||||
if noCopy {
|
||||
return wrc, nil
|
||||
}
|
||||
|
||||
if !iics.routedToDirect && tls_lazy_encrypt {
|
||||
|
||||
// 我们加了回落之后,就无法确定 “未使用tls的outClient 一定是在服务端” 了
|
||||
@@ -1033,8 +1040,8 @@ advLayerStep:
|
||||
if client.IsUseTLS() {
|
||||
//必须是 UserClient
|
||||
if userClient := client.(proxy.UserClient); userClient != nil {
|
||||
tryRawCopy(false, userClient, nil, nil, wrc, wlc, iics.baseLocalConn, true, clientEndRemoteClientTlsRawReadRecorder)
|
||||
return
|
||||
tryTlsLazyRawCopy(false, userClient, nil, nil, wrc, wlc, iics.baseLocalConn, true, clientEndRemoteClientTlsRawReadRecorder)
|
||||
return nil, &utils.NumErr{N: 11, Prefix: "dialClient err, "}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1044,8 +1051,8 @@ advLayerStep:
|
||||
// 否则将无法开启splice功能。这是为了防止0-rtt 探测;
|
||||
|
||||
if userServer, ok := iics.inServer.(proxy.UserServer); ok {
|
||||
tryRawCopy(false, nil, userServer, nil, wrc, wlc, iics.baseLocalConn, false, iics.inServerTlsRawReadRecorder)
|
||||
return
|
||||
tryTlsLazyRawCopy(false, nil, userServer, nil, wrc, wlc, iics.baseLocalConn, false, iics.inServerTlsRawReadRecorder)
|
||||
return nil, &utils.NumErr{N: 12, Prefix: "dialClient err, "}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1085,4 +1092,5 @@ advLayerStep:
|
||||
netLayer.Relay(wlc, wrc)
|
||||
}
|
||||
|
||||
return wrc, nil
|
||||
}
|
||||
|
||||
@@ -120,10 +120,10 @@ func NewAddrByURL(addrStr string) (*Addr, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
//会根据thing的类型 生成实际addr; 可以为数字端口,或者带冒号的字符串,或者一个 文件路径(uds)
|
||||
//会根据thing的类型 生成实际addr; 可以为数字端口,或者带冒号的字符串,或者一个 文件路径(unix domain socket)
|
||||
func NewAddrFromAny(thing any) (addr *Addr, err error) {
|
||||
var integer int
|
||||
var dest_type byte = 0 //0: port, 1: ip:port, 2: uds
|
||||
var dest_type byte = 0 //0: port, 1: ip:port, 2: unix domain socket
|
||||
var dest_string string
|
||||
|
||||
switch value := thing.(type) {
|
||||
@@ -227,10 +227,6 @@ func (a *Addr) IsUDP() bool {
|
||||
}
|
||||
|
||||
func (a *Addr) ToUDPAddr() *net.UDPAddr {
|
||||
if !a.IsUDP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ua, err := net.ResolveUDPAddr("udp", a.String())
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -264,6 +260,7 @@ func (addr *Addr) Dial() (net.Conn, error) {
|
||||
}
|
||||
|
||||
// Returned address bytes and type
|
||||
// 如果atyp类型是 域名,则 第一字节为该域名的总长度, 其余字节为域名内容。
|
||||
func (a *Addr) AddressBytes() ([]byte, byte) {
|
||||
var addr []byte
|
||||
var atyp byte
|
||||
@@ -293,7 +290,7 @@ func (a *Addr) AddressBytes() ([]byte, byte) {
|
||||
|
||||
// ParseAddr 分析字符串,并按照特定方式返回 地址类型 atyp,地址数据 addr []byte,以及端口号,
|
||||
// 如果解析出的地址是ip,则 addr返回 net.IP;
|
||||
// 如果解析出的地址是 域名,则第一字节为AtypDomain, 剩余字节为域名内容
|
||||
// 如果解析出的地址是 域名,则第一字节为域名总长度, 剩余字节为域名内容
|
||||
func ParseStrToAddr(s string) (atyp byte, addr []byte, port_uint16 uint16, err error) {
|
||||
|
||||
var host string
|
||||
|
||||
@@ -18,7 +18,7 @@ type Splicer interface {
|
||||
CanSplice() (bool, net.Conn) //当前状态是否可以splice
|
||||
}
|
||||
|
||||
//这里认为能 splice 或 sendfile的 都算,具体可参考go标准代码的实现, 总之就是tcp和uds可以.
|
||||
//这里认为能 splice 或 sendfile的 都算,具体可参考go标准代码的实现, 总之就是tcp和 unix domain socket 可以.
|
||||
// 若不是基本Conn,则会试图转换为Splicer并获取底层Conn
|
||||
func CanSpliceDirectly(r any) bool {
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const (
|
||||
//本文件内含 一些 转发 udp 数据的 接口与方法
|
||||
|
||||
// 阻塞.
|
||||
func RelayUDP(putter UDP_Putter, extractor UDP_Extractor) {
|
||||
func RelayUDP(putter UDP_Putter, extractor UDP_Extractor, dialFunc func(targetAddr *Addr) (io.ReadWriter, error)) {
|
||||
|
||||
go func() {
|
||||
for {
|
||||
@@ -21,7 +21,7 @@ func RelayUDP(putter UDP_Putter, extractor UDP_Extractor) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = putter.WriteUDPRequest(raddr, bs)
|
||||
err = putter.WriteUDPRequest(raddr, bs, dialFunc)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -58,9 +58,10 @@ type UDP_Extractor interface {
|
||||
UDPResponseWriter
|
||||
}
|
||||
|
||||
// 写入一个UDP请求
|
||||
// 写入一个UDP请求; 可以包裹成任意协议。
|
||||
// 因为有时该地址从来没申请过,所以此时就要用dialFunc创建一个新连接
|
||||
type UDPRequestWriter interface {
|
||||
WriteUDPRequest(*net.UDPAddr, []byte) error
|
||||
WriteUDPRequest(target *net.UDPAddr, request []byte, dialFunc func(targetAddr *Addr) (io.ReadWriter, error)) error
|
||||
}
|
||||
|
||||
//拉取一个新的 UDP 响应
|
||||
@@ -70,7 +71,7 @@ type UDPResponseReader interface {
|
||||
|
||||
// UDP_Putter, 用于把 udp请求转换成 虚拟的协议
|
||||
//
|
||||
// 向一个特定的协议 写入 UDP请求,然后试图读取 该请求的回应
|
||||
// 向一个特定的协议 写入 UDP请求,然后试图读取 该请求的回应. 比如vless.Client就实现了它
|
||||
type UDP_Putter interface {
|
||||
UDPRequestWriter
|
||||
UDPResponseReader
|
||||
@@ -100,7 +101,7 @@ func (e *UniUDP_Putter) GetNewUDPResponse() (*net.UDPAddr, []byte, error) {
|
||||
return e.targetAddr, bs[:n], nil
|
||||
}
|
||||
|
||||
func (e *UniUDP_Putter) WriteUDPRequest(addr *net.UDPAddr, bs []byte) (err error) {
|
||||
func (e *UniUDP_Putter) WriteUDPRequest(addr *net.UDPAddr, bs []byte, dialFunc func(targetAddr *Addr) (io.ReadWriter, error)) (err error) {
|
||||
|
||||
if addr.String() == e.targetAddr.String() {
|
||||
_, err = e.ReadWriter.Write(bs)
|
||||
@@ -110,8 +111,9 @@ func (e *UniUDP_Putter) WriteUDPRequest(addr *net.UDPAddr, bs []byte) (err error
|
||||
if e.unknownRemoteAddrMsgWriter == nil {
|
||||
return
|
||||
}
|
||||
// 普通的 WriteUDPRequest需要调用 dialFunc来拨号新链接,而我们这里 直接就传递给 unknownRemoteAddrMsgWriter 了
|
||||
|
||||
return e.unknownRemoteAddrMsgWriter.WriteUDPRequest(addr, bs)
|
||||
return e.unknownRemoteAddrMsgWriter.WriteUDPRequest(addr, bs, dialFunc)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -206,7 +208,7 @@ func (u *UDP_Pipe) WriteUDPResponse(addr *net.UDPAddr, bs []byte) error {
|
||||
}
|
||||
|
||||
// 会保存bs的副本,不必担心数据被改变的问题。
|
||||
func (u *UDP_Pipe) WriteUDPRequest(addr *net.UDPAddr, bs []byte) error {
|
||||
func (u *UDP_Pipe) WriteUDPRequest(addr *net.UDPAddr, bs []byte, dialFunc func(targetAddr *Addr) (io.ReadWriter, error)) error {
|
||||
bsCopy := make([]byte, len(bs))
|
||||
copy(bsCopy, bs)
|
||||
|
||||
@@ -270,6 +272,7 @@ func RelayUDP_to_Direct(extractor UDP_Extractor) {
|
||||
go func(thisconn *net.UDPConn, supposedRemoteAddr *net.UDPAddr) {
|
||||
bs := make([]byte, MaxUDP_packetLen)
|
||||
for {
|
||||
//log.Println("redirect udp, start read", supposedRemoteAddr)
|
||||
n, raddr, err := thisconn.ReadFromUDP(bs)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -284,6 +287,8 @@ func RelayUDP_to_Direct(extractor UDP_Extractor) {
|
||||
dialedUDPConnMap[raddr.String()] = thisconn
|
||||
mutex.Unlock()
|
||||
|
||||
//log.Println("redirect udp, will write to extractor", string(bs[:n]))
|
||||
|
||||
err = extractor.WriteUDPResponse(raddr, bs[:n])
|
||||
if err != nil {
|
||||
break
|
||||
|
||||
@@ -29,7 +29,7 @@ type CommonConf struct {
|
||||
Insecure bool `toml:"insecure"` //tls 是否安全
|
||||
Alpn []string `toml:"alpn"`
|
||||
|
||||
Network string `toml:"network"` //默认使用tcp, network可选值为 tcp,udp, uds;
|
||||
Network string `toml:"network"` //默认使用tcp, network可选值为 tcp, udp, unix;
|
||||
|
||||
AdvancedLayer string `toml:"advancedLayer"` //可不填,或者为ws,或者为grpc
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ func init() {
|
||||
proxy.RegisterClient(name, &ClientCreator{})
|
||||
}
|
||||
|
||||
//实现了 proxy.Client, netLayer.UDP_Extractor, netLayer.UDP_Putter
|
||||
type Direct struct {
|
||||
proxy.ProxyCommonStruct
|
||||
*netLayer.UDP_Pipe
|
||||
@@ -30,6 +31,10 @@ func NewClient() (proxy.Client, error) {
|
||||
d := &Direct{
|
||||
UDP_Pipe: netLayer.NewUDP_Pipe(),
|
||||
}
|
||||
//单单的pipe是无法做到转发的,它就像一个缓存一样;
|
||||
// 一方是未知的, 将 Direct 视为 UDP_Putter, 放入请求,
|
||||
// 然后我们这边就要通过一个 goroutine 来不断提取请求然后转发到direct.
|
||||
|
||||
go netLayer.RelayUDP_to_Direct(d.UDP_Pipe)
|
||||
return d, nil
|
||||
}
|
||||
@@ -44,6 +49,7 @@ func (_ ClientCreator) NewClient(*proxy.DialConf) (proxy.Client, error) {
|
||||
|
||||
func (d *Direct) Name() string { return name }
|
||||
|
||||
//若 underlay 为nil,则我们会自动对target进行拨号。
|
||||
func (d *Direct) Handshake(underlay net.Conn, target *netLayer.Addr) (io.ReadWriter, error) {
|
||||
|
||||
if underlay == nil {
|
||||
|
||||
@@ -75,7 +75,7 @@ type ProxyCommon interface {
|
||||
|
||||
/////////////////// 网络层/传输层 ///////////////////
|
||||
|
||||
// 地址,若tcp/udp的话则为 ip:port/host:port的形式, 若是uds则是文件路径 ,
|
||||
// 地址,若tcp/udp的话则为 ip:port/host:port的形式, 若是 unix domain socket 则是文件路径 ,
|
||||
// 在server就是监听地址,在client就是拨号地址
|
||||
AddrStr() string
|
||||
SetAddrStr(string)
|
||||
|
||||
165
proxy/socks5/client.go
Normal file
165
proxy/socks5/client.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/hahahrfool/v2ray_simple/netLayer"
|
||||
"github.com/hahahrfool/v2ray_simple/utils"
|
||||
)
|
||||
|
||||
//为了安全, 我们不支持socks5作为 proxy.Client;
|
||||
// 不过为了测试 udp associate 需要我们需要模拟一下socks5请求
|
||||
|
||||
func Client_EstablishUDPAssociate(conn net.Conn) (port int, err error) {
|
||||
var ba [10]byte
|
||||
|
||||
//握手阶段
|
||||
ba[0] = Version5
|
||||
ba[1] = 1
|
||||
ba[2] = 0
|
||||
_, err = conn.Write(ba[:3])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n, err := conn.Read(ba[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != 2 || ba[0] != Version5 || ba[1] != 0 {
|
||||
return 0, &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1}
|
||||
}
|
||||
|
||||
//请求udp associate 阶段
|
||||
|
||||
ba[0] = Version5
|
||||
ba[1] = CmdUDPAssociate
|
||||
ba[2] = 0
|
||||
ba[3] = ATypIP4
|
||||
ba[4] = 0
|
||||
ba[5] = 0
|
||||
ba[6] = 0
|
||||
ba[7] = 0
|
||||
ba[8] = 0 //port
|
||||
ba[9] = 0 //port
|
||||
// 按理说要告诉服务端我们要用到的ip和端口,但是我们不知道,所以全填零
|
||||
|
||||
_, err = conn.Write(ba[:10])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n, err = conn.Read(ba[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n != 10 || ba[0] != Version5 || ba[1] != 0 || ba[2] != 0 || ba[3] != 1 || ba[4] != 0 || ba[5] != 0 || ba[6] != 0 || ba[7] != 0 {
|
||||
return 0, &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2}
|
||||
}
|
||||
|
||||
port = int(ba[8])<<8 | int(ba[9])
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// RequestUDP 向一个 socks5服务器监听的 udp端口发送一次udp请求
|
||||
//在udp associate结束后,就已经知道了服务器给我们专用的port了,向这个端口发送一个udp请求.
|
||||
//
|
||||
// 另外的备忘是, 服务器返回的数据使用了相同的结构
|
||||
func Client_RequestUDP(udpConn *net.UDPConn, target *netLayer.Addr, data []byte) {
|
||||
|
||||
if udpConn == nil {
|
||||
//不允许
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteByte(0)
|
||||
buf.WriteByte(0)
|
||||
buf.WriteByte(0)
|
||||
|
||||
abs, atype := target.AddressBytes()
|
||||
switch atype {
|
||||
case 0:
|
||||
//无效地址
|
||||
|
||||
case netLayer.AtypIP4:
|
||||
buf.WriteByte(ATypIP4)
|
||||
case netLayer.AtypIP6:
|
||||
buf.WriteByte(ATypIP6)
|
||||
|
||||
case netLayer.AtypDomain:
|
||||
buf.WriteByte(ATypDomain)
|
||||
|
||||
}
|
||||
buf.Write(abs)
|
||||
|
||||
port := target.Port
|
||||
|
||||
buf.WriteByte(byte(int16(port) >> 8))
|
||||
buf.WriteByte(byte(int16(port) << 8 >> 8))
|
||||
|
||||
buf.Write(data)
|
||||
|
||||
udpConn.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
//从 一个 socks5服务器的udp端口 读取一次 udp回应
|
||||
func Client_ReadUDPResponse(udpConn *net.UDPConn, supposedServerAddr *net.UDPAddr) (target netLayer.Addr, data []byte, e error) {
|
||||
|
||||
buf := utils.GetPacket()
|
||||
n, addr, err := udpConn.ReadFromUDP(buf)
|
||||
//log.Println("Client_ReadUDPResponse, got data")
|
||||
|
||||
if err != nil {
|
||||
e = err
|
||||
return
|
||||
}
|
||||
if n < 6 {
|
||||
e = errors.New("UDPConn short read err")
|
||||
return
|
||||
}
|
||||
|
||||
if !(addr.IP.Equal(supposedServerAddr.IP) && addr.Port == supposedServerAddr.Port) {
|
||||
e = utils.NewDataErr("socks5 Client_ReadUDPResponse , got data from unknown source", nil, addr)
|
||||
return
|
||||
}
|
||||
if buf[0] != 0 || buf[1] != 0 || buf[2] != 0 {
|
||||
e = &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 1}
|
||||
return
|
||||
}
|
||||
atype := buf[3]
|
||||
remainBuf := bytes.NewBuffer(buf[4:n])
|
||||
|
||||
switch atype {
|
||||
case ATypIP4:
|
||||
ipbs := make([]byte, 4)
|
||||
remainBuf.Read(ipbs)
|
||||
target.IP = ipbs
|
||||
case ATypIP6:
|
||||
ipbs := make([]byte, net.IPv6len)
|
||||
remainBuf.Read(ipbs)
|
||||
target.IP = ipbs
|
||||
case ATypDomain:
|
||||
nameLen, _ := remainBuf.ReadByte()
|
||||
nameBuf := make([]byte, nameLen)
|
||||
remainBuf.Read(nameBuf)
|
||||
|
||||
target.Name = string(nameBuf)
|
||||
|
||||
default:
|
||||
e = &utils.NumErr{Prefix: "EstablishUDPAssociate,protocol err", N: 2}
|
||||
return
|
||||
}
|
||||
|
||||
pb1, _ := remainBuf.ReadByte()
|
||||
pb2, _ := remainBuf.ReadByte()
|
||||
|
||||
target.Port = int(pb1)<<8 | int(pb2)
|
||||
|
||||
data = remainBuf.Bytes()
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
@@ -41,15 +41,12 @@ func (_ ServerCreator) NewServer(dc *proxy.ListenConf) (proxy.Server, error) {
|
||||
|
||||
func (s *Server) Name() string { return Name }
|
||||
|
||||
func (s *Server) CanFallback() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
//English: https://www.ietf.org/rfc/rfc1928.txt
|
||||
|
||||
//中文: https://aber.sh/articles/Socks5/
|
||||
// 参考 https://studygolang.com/articles/31404
|
||||
|
||||
// 处理tcp收到的请求. 注意, udp associate后的 udp请求并不通过此函数处理, 而是由 UDPConn.StartReadRequest 处理
|
||||
func (s *Server) Handshake(underlay net.Conn) (io.ReadWriter, *netLayer.Addr, error) {
|
||||
// Set handshake timeout 4 seconds
|
||||
if err := underlay.SetReadDeadline(time.Now().Add(time.Second * 4)); err != nil {
|
||||
@@ -199,15 +196,16 @@ type UDPConn struct {
|
||||
*net.UDPConn
|
||||
clientSupposedAddr *netLayer.Addr //客户端指定的客户端自己未来将使用的公网UDP的Addr
|
||||
|
||||
clientSupposedAddrIsNothing bool
|
||||
//clientSupposedAddrIsNothing bool
|
||||
}
|
||||
|
||||
// 阻塞
|
||||
// 从 udpPutter.GetNewUDPResponse 循环阅读 所有需要发送给客户端的 数据,然后发送给客户端
|
||||
// 这些响应数据是 由其它设施 写入 udpProxy的
|
||||
// 从 udpPutter.GetNewUDPResponse 循环阅读 所有需要发送给客户端的 数据,然后通过 u.UDPConn.Write 发送给客户端
|
||||
// 这些响应数据是 在其它地方 写入 udpPutter 的, 本 u 是不管 谁、如何 把这个信息 放进去的。
|
||||
func (u *UDPConn) StartPushResponse(udpPutter netLayer.UDP_Putter) {
|
||||
for {
|
||||
raddr, bs, err := udpPutter.GetNewUDPResponse()
|
||||
//log.Println("StartPushResponse got new response", raddr, string(bs), err)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -227,20 +225,30 @@ func (u *UDPConn) StartPushResponse(udpPutter netLayer.UDP_Putter) {
|
||||
buf.WriteByte(byte(int16(raddr.Port) << 8 >> 8))
|
||||
buf.Write(bs)
|
||||
|
||||
_, err = u.UDPConn.Write(buf.Bytes())
|
||||
//log.Println("StartPushResponse, start write", u.clientSupposedAddr.ToUDPAddr())
|
||||
|
||||
//_, err = u.UDPConn.Write(buf.Bytes()) //必须要指明raddr
|
||||
_, err = u.UDPConn.WriteToUDP(buf.Bytes(), u.clientSupposedAddr.ToUDPAddr())
|
||||
|
||||
if err != nil {
|
||||
//log.Println("StartPushResponse, write err ", err)
|
||||
break
|
||||
}
|
||||
|
||||
//log.Println("StartPushResponse, written")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 阻塞
|
||||
// 监听 与客户端的udp连接;循环查看客户端发来的请求信息; 然后将该请求 用 udpPutter.WriteUDPRequest 发送给 udpPutter
|
||||
// 监听 与客户端的udp连接 (u.UDPConn);循环查看客户端发来的请求信息;
|
||||
// 然后将该请求 用 udpPutter.WriteUDPRequest 发送给 udpPutter
|
||||
// 至于fullcone与否它是不管的。
|
||||
func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter) {
|
||||
func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter, dialFunc func(targetAddr *netLayer.Addr) (io.ReadWriter, error)) {
|
||||
|
||||
var clientSupposedAddrIsNothing bool
|
||||
if len(u.clientSupposedAddr.IP) < 3 || u.clientSupposedAddr.IP.IsUnspecified() {
|
||||
u.clientSupposedAddrIsNothing = true
|
||||
clientSupposedAddrIsNothing = true
|
||||
}
|
||||
|
||||
bs := make([]byte, netLayer.MaxUDP_packetLen)
|
||||
@@ -253,6 +261,7 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter) {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if n < 6 {
|
||||
if utils.CanLogWarn() {
|
||||
|
||||
@@ -261,7 +270,7 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !u.clientSupposedAddrIsNothing && !addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port {
|
||||
if !clientSupposedAddrIsNothing && (!addr.IP.Equal(u.clientSupposedAddr.IP) || addr.Port != u.clientSupposedAddr.Port) {
|
||||
|
||||
//just random attack message.
|
||||
continue
|
||||
@@ -321,7 +330,14 @@ func (u *UDPConn) StartReadRequest(udpPutter netLayer.UDP_Putter) {
|
||||
Network: "udp",
|
||||
}
|
||||
|
||||
udpPutter.WriteUDPRequest(thisaddr.ToUDPAddr(), bs[newStart:n-newStart])
|
||||
if clientSupposedAddrIsNothing {
|
||||
clientSupposedAddrIsNothing = false
|
||||
u.clientSupposedAddr = netLayer.NewAddrFromUDPAddr(addr)
|
||||
}
|
||||
|
||||
//log.Println("socks5 server,StartReadRequest, got msg", thisaddr, string(bs[newStart:n]))
|
||||
|
||||
udpPutter.WriteUDPRequest(thisaddr.ToUDPAddr(), bs[newStart:n], dialFunc)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
154
proxy/socks5/udp_test.go
Normal file
154
proxy/socks5/udp_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package socks5_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hahahrfool/v2ray_simple/netLayer"
|
||||
"github.com/hahahrfool/v2ray_simple/proxy/direct"
|
||||
"github.com/hahahrfool/v2ray_simple/proxy/socks5"
|
||||
)
|
||||
|
||||
//tcp就不测了,我们实践直接测试完全好使,这里重点测试UDP
|
||||
// 因为chrome也是无法通过 socks5去申请udp链接的,所以没法自己用浏览器测试
|
||||
|
||||
func TestUDP(t *testing.T) {
|
||||
|
||||
s := &socks5.Server{}
|
||||
|
||||
//建立socks5服务并监听,这里仅用于 udp associate 握手
|
||||
sAddrStr := netLayer.GetRandLocalAddr()
|
||||
listener, err := net.Listen("tcp", sAddrStr)
|
||||
if err != nil {
|
||||
t.Log("can not listen on", sAddrStr, err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
direct, _ := direct.NewClient()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
lc, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Logf("failed in accept: %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
t.Log("socks5 server got new conn")
|
||||
wlc, targetAddr, err := s.Handshake(lc)
|
||||
if targetAddr.IsUDP() {
|
||||
t.Log("socks5 server got udp associate")
|
||||
}
|
||||
//此时wlc返回的是socks5新监听的 conn
|
||||
|
||||
udpConn := wlc.(*socks5.UDPConn)
|
||||
|
||||
if putter := direct.(netLayer.UDP_Putter); putter != nil {
|
||||
|
||||
//UDP_Putter 不使用传统的Handshake过程,因为Handshake是用于第一次数据,然后后面接着的双向传输都不再需要额外信息;而 UDP_Putter 每一次数据传输都是需要传输 目标地址的,所以每一次都需要一些额外数据,这就是我们 UDP_Putter 接口去解决的事情。
|
||||
|
||||
//因为UDP Associate后,就会保证以后的向 wlc 的 所有请求数据都是udp请求,所以可以在这里直接循环转发了。
|
||||
|
||||
go udpConn.StartPushResponse(putter)
|
||||
|
||||
dialFunc := func(targetAddr *netLayer.Addr) (io.ReadWriter, error) {
|
||||
return targetAddr.Dial()
|
||||
}
|
||||
|
||||
udpConn.StartReadRequest(putter, dialFunc)
|
||||
|
||||
} else {
|
||||
|
||||
t.Log("socks5 server -> client for udp, but client didn't implement netLayer.UDP_Putter", direct.Name())
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//建立虚拟目标udp服务器并监听
|
||||
fakeUDP_ServerPort := netLayer.RandPort()
|
||||
|
||||
fakeRealUDPServerListener, err := net.ListenUDP("udp4", &net.UDPAddr{
|
||||
IP: net.IPv4(0, 0, 0, 0),
|
||||
Port: fakeUDP_ServerPort,
|
||||
})
|
||||
if err != nil {
|
||||
t.Log("监听失败 udp ", err)
|
||||
t.FailNow()
|
||||
}
|
||||
defer fakeRealUDPServerListener.Close()
|
||||
|
||||
go func() {
|
||||
readbuf := make([]byte, 10*1024)
|
||||
|
||||
for {
|
||||
t.Log(" udp for! ")
|
||||
// 读取数据, 无视任何信息,直接返回 "reply"
|
||||
n, remoteAddr, err := fakeRealUDPServerListener.ReadFromUDP(readbuf)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "use of closed network connection") {
|
||||
t.Log("udp server read connection closed")
|
||||
return
|
||||
} else {
|
||||
t.Log("udp server 读取数据失败!", err)
|
||||
}
|
||||
|
||||
//continue
|
||||
break
|
||||
}
|
||||
t.Log("fake udp server got", remoteAddr, string(readbuf[:n]))
|
||||
|
||||
_, err = fakeRealUDPServerListener.WriteToUDP([]byte("reply"), remoteAddr)
|
||||
if err != nil {
|
||||
t.Log("udp write back err:", err)
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("fake udp server written")
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
//尝试客户端 UDPAssociate 发起请求
|
||||
rc, _ := net.Dial("tcp", sAddrStr)
|
||||
defer rc.Close()
|
||||
|
||||
socks5_ServerPort, err := socks5.Client_EstablishUDPAssociate(rc)
|
||||
if err != nil {
|
||||
t.Log("Client_EstablishUDPAssociate failed", err)
|
||||
t.FailNow()
|
||||
}
|
||||
t.Log("Server Port", socks5_ServerPort)
|
||||
|
||||
//获知 服务器udp端口后,再向该端口发起请求
|
||||
|
||||
raSocks5, _ := netLayer.NewAddr("127.0.0.1:" + strconv.Itoa(socks5_ServerPort))
|
||||
|
||||
raSocks5UDPAddr := raSocks5.ToUDPAddr()
|
||||
|
||||
urc, err := net.DialUDP("udp", nil, raSocks5UDPAddr)
|
||||
if err != nil {
|
||||
t.Log("DialUDP failed", raSocks5UDPAddr)
|
||||
t.FailNow()
|
||||
}
|
||||
defer urc.Close()
|
||||
|
||||
raFake, _ := netLayer.NewAddr("127.0.0.1:" + strconv.Itoa(fakeUDP_ServerPort))
|
||||
|
||||
t.Log("call Client_RequestUDP")
|
||||
|
||||
socks5.Client_RequestUDP(urc, raFake, []byte("hello"))
|
||||
|
||||
t.Log("call Client_ReadUDPResponse")
|
||||
|
||||
ta, data, err := socks5.Client_ReadUDPResponse(urc, raSocks5UDPAddr)
|
||||
if err != nil {
|
||||
t.Log("Client_ReadUDPResponse failed", err)
|
||||
t.FailNow()
|
||||
}
|
||||
t.Log(ta, string(data))
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func init() {
|
||||
proxy.RegisterClient(Name, &ClientCreator{})
|
||||
}
|
||||
|
||||
//实现 proxy.UserClient
|
||||
//实现 proxy.UserClient,以及 netLayer.UDP_Putter
|
||||
type Client struct {
|
||||
proxy.ProxyCommonStruct
|
||||
|
||||
@@ -55,13 +55,15 @@ func (_ ClientCreator) NewClient(dc *proxy.DialConf) (proxy.Client, error) {
|
||||
c := &Client{
|
||||
user: id,
|
||||
}
|
||||
|
||||
c.knownUDPDestinations = make(map[string]io.ReadWriter)
|
||||
c.udpResponseChan = make(chan *netLayer.UDPAddrData, 20)
|
||||
|
||||
v := dc.Version
|
||||
if v >= 0 {
|
||||
|
||||
if v == 1 {
|
||||
c.version = 1
|
||||
c.knownUDPDestinations = make(map[string]io.ReadWriter)
|
||||
c.udpResponseChan = make(chan *netLayer.UDPAddrData, 20)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,6 +81,8 @@ func NewClientByURL(url *url.URL) (proxy.Client, error) {
|
||||
c := &Client{
|
||||
user: id,
|
||||
}
|
||||
c.knownUDPDestinations = make(map[string]io.ReadWriter)
|
||||
c.udpResponseChan = make(chan *netLayer.UDPAddrData, 20)
|
||||
|
||||
vStr := url.Query().Get("version")
|
||||
if vStr != "" {
|
||||
@@ -86,8 +90,6 @@ func NewClientByURL(url *url.URL) (proxy.Client, error) {
|
||||
if err == nil {
|
||||
if v == 1 {
|
||||
c.version = 1
|
||||
c.knownUDPDestinations = make(map[string]io.ReadWriter)
|
||||
c.udpResponseChan = make(chan *netLayer.UDPAddrData, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +107,7 @@ func (c *Client) Handshake(underlay net.Conn, target *netLayer.Addr) (io.ReadWri
|
||||
var err error
|
||||
|
||||
if underlay == nil {
|
||||
underlay, err = target.Dial()
|
||||
underlay, err = target.Dial() //不建议传入underlay为nil,因为这里dial处于裸奔状态
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,14 +178,13 @@ func (c *Client) Handshake(underlay net.Conn, target *netLayer.Addr) (io.ReadWri
|
||||
}, err
|
||||
}
|
||||
|
||||
// 注意调用此方法前要判断是否是v1
|
||||
func (c *Client) GetNewUDPResponse() (*net.UDPAddr, []byte, error) {
|
||||
x := <-c.udpResponseChan //由 handle_CRUMFURS 以及 WriteUDPRequest 中的 goroutine 填充
|
||||
x := <-c.udpResponseChan //v1的话,由 handle_CRUMFURS 以及 WriteUDPRequest 中的 goroutine 填充;v0的话,由 WriteUDPRequest 填充
|
||||
return x.Addr, x.Data, nil
|
||||
}
|
||||
|
||||
// 注意调用此方法前要判断是否是v1
|
||||
func (c *Client) WriteUDPRequest(a *net.UDPAddr, b []byte) (err error) {
|
||||
//一般由socks5或者透明代理等地方 获取到 udp请求后,被传入这里
|
||||
func (c *Client) WriteUDPRequest(a *net.UDPAddr, b []byte, dialFunc func(targetAddr *netLayer.Addr) (io.ReadWriter, error)) (err error) {
|
||||
|
||||
astr := a.String()
|
||||
|
||||
@@ -193,13 +194,15 @@ func (c *Client) WriteUDPRequest(a *net.UDPAddr, b []byte) (err error) {
|
||||
|
||||
if knownConn == nil {
|
||||
|
||||
//这里调用 c.Handshake,会自动帮我们拨号代理节点CmdUDP
|
||||
knownConn, err = dialFunc(netLayer.NewAddrFromUDPAddr(a))
|
||||
if err != nil || knownConn == nil {
|
||||
return utils.NewErr("vless WriteUDPRequest, err when creating an underlay", err)
|
||||
}
|
||||
//这里原来的代码是调用 c.Handshake,会自动帮我们拨号代理节点CmdUDP
|
||||
// 但是似乎有问题,因为不应该由client自己拨号vless,因为我们还有上层的tls;
|
||||
// 自己拨号的话,那就是裸奔状态
|
||||
knownConn, err = c.Handshake(nil, netLayer.NewAddrFromUDPAddr(a))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 最新代码采用dialFunc的方式解决
|
||||
//knownConn, err = c.Handshake(newUnderlay, netLayer.NewAddrFromUDPAddr(a))
|
||||
|
||||
c.mutex.Lock()
|
||||
c.knownUDPDestinations[astr] = knownConn
|
||||
|
||||
@@ -133,6 +133,7 @@ func (c *UserConn) WriteBuffers(buffers [][]byte) (int64, error) {
|
||||
|
||||
}
|
||||
|
||||
//专门适用于 裸奔splice的情况
|
||||
func (uc *UserConn) ReadFrom(r io.Reader) (written int64, err error) {
|
||||
if uc.isUDP {
|
||||
return netLayer.ClassicReadFrom(uc, r)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package vless
|
||||
package vless_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -52,7 +52,7 @@ func testVLess(version int, port string, t *testing.T) {
|
||||
t.Logf("failed in accept: %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
t.Log("vless sever got new conn")
|
||||
t.Log("vless server got new conn")
|
||||
go func() {
|
||||
defer lc.Close()
|
||||
wlc, targetAddr, err := server.Handshake(lc)
|
||||
@@ -139,7 +139,7 @@ func testVLessUDP(version int, port string, t *testing.T) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
thePort := 30002
|
||||
thePort := netLayer.RandPort()
|
||||
|
||||
fakeRealUDPServerListener, err := net.ListenUDP("udp4", &net.UDPAddr{
|
||||
IP: net.IPv4(0, 0, 0, 0),
|
||||
|
||||
10
test_test.go
10
test_test.go
@@ -90,25 +90,25 @@ key = "cert.key"
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
//先建立服务端监听,再建立客户端监听,最后自定义dns查询 并导向 客户端的 dokodemo监听端口
|
||||
//先建立服务端监听和客户端监听,最后自定义dns查询 并导向 客户端的 dokodemo监听端口
|
||||
|
||||
//vless
|
||||
//vless in
|
||||
serverEndInServer, err := proxy.NewServer(serverConf.Listen[0])
|
||||
if err != nil {
|
||||
log.Fatalln("can not create local server: ", err)
|
||||
}
|
||||
// direct
|
||||
// direct out
|
||||
serverEndOutClient, err := proxy.NewClient(serverConf.Dial[0])
|
||||
if err != nil {
|
||||
log.Fatalln("can not create local server: ", err)
|
||||
}
|
||||
|
||||
//domodemo
|
||||
//domodemo in
|
||||
clientEndInServer, err := proxy.NewServer(clientConf.Listen[0])
|
||||
if err != nil {
|
||||
log.Fatalln("can not create local server: ", err)
|
||||
}
|
||||
// vless
|
||||
// vless out
|
||||
clientEndOutClient, err := proxy.NewClient(clientConf.Dial[0])
|
||||
if err != nil {
|
||||
log.Fatalln("can not create local server: ", err)
|
||||
|
||||
@@ -35,11 +35,11 @@ func canNetwork_tlsLazy(nw string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// tryRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn,也就是裸奔转发
|
||||
// tryTlsLazyRawCopy 尝试能否直接对拷,对拷 直接使用 原始 TCPConn,也就是裸奔转发
|
||||
// 如果在linux上,则和 xtls的splice 含义相同. 在其他系统时,与xtls-direct含义相同。
|
||||
// 我们内部先 使用 DetectConn进行过滤分析,然后再判断进化为splice 或者退化为普通拷贝
|
||||
// 第一个参数仅用于 tls_lazy_secure
|
||||
func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, targetAddr *netLayer.Addr, wrc, wlc io.ReadWriter, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) {
|
||||
func tryTlsLazyRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_server proxy.UserServer, targetAddr *netLayer.Addr, wrc, wlc io.ReadWriter, localConn net.Conn, isclient bool, theRecorder *tlsLayer.Recorder) {
|
||||
if utils.CanLogDebug() {
|
||||
log.Println("trying tls lazy copy")
|
||||
}
|
||||
@@ -50,8 +50,6 @@ func tryRawCopy(useSecureMethod bool, proxy_client proxy.UserClient, proxy_serve
|
||||
// 之所以可以对拷直连,是因为无论是 socks5 还是vless,只是在最开始的部分 加了目标头,后面的所有tcp连接都是直接传输的数据,就是说,一开始握手什么的是不能直接对拷的,等到后期就可以了
|
||||
// 而且之所以能对拷,还有个原因就是,远程服务器 与 客户端 总是源源不断地 为 我们的 原始 TCP 连接 提供数据,我们只是一个中间商而已,左手倒右手
|
||||
|
||||
// 如果开启了 half lazy 开关,则会在 Write的那一端 加强过滤,过滤一些alert(目前还没做),然后 只在Read端 进行splice
|
||||
//
|
||||
// 如果是客户端,则 从 wlc 读取,写入 wrc ,这种情况是 Write, 然后对于 DetectConn 来说是 Read,即 从DetectConn读取,然后 写入到远程连接
|
||||
// 如果是服务端,则 从 wrc 读取,写入 wlc, 这种情况是 Write
|
||||
//
|
||||
|
||||
@@ -3,8 +3,21 @@ package utils
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
//没啥特殊的
|
||||
type NumErr struct {
|
||||
N int
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func (ne *NumErr) Error() string {
|
||||
|
||||
return ne.Prefix + strconv.Itoa(ne.N)
|
||||
}
|
||||
|
||||
//就是带个buffer的普通ErrInErr,没啥特殊的
|
||||
type ErrFirstBuffer struct {
|
||||
Err error
|
||||
First *bytes.Buffer
|
||||
|
||||
14
vless_v1.md
14
vless_v1.md
@@ -3,9 +3,9 @@
|
||||
目前v1仍然处于研发当中。建议先用v0,等v1完全出炉了再说,本文只是理论探索,实际代码暂未完整实现所有的设计.
|
||||
## 握手协议格式
|
||||
|
||||
具体我的探讨还可以查看 https://github.com/v2fly/v2ray-core/discussions/1655
|
||||
具体我的探讨的一部分还可以查看 https://github.com/v2fly/v2ray-core/discussions/1655
|
||||
|
||||
总的来说 vless v1 简化了一些流程, 约定永远使用tls(与trojan相同),并重点考虑 非多路复用的 udp over tcp的 fullcone实现。
|
||||
总的来说 vless v1 简化了一些流程, 并重点考虑 非多路复用的 udp over tcp的 fullcone实现。
|
||||
|
||||
除了fullcone,我还想到了关于 内层加密, 连接池,以及 自动dns的 对协议的改进,请阅读全文了解详情。
|
||||
|
||||
@@ -31,7 +31,7 @@ https://www.zhihu.com/question/29916578
|
||||
|
||||
所以,我规定 vless v1 的udp实现中,会判断连接本身,如果连接 使用的是websocket或者类似的本身就加了数据长度头的,则udp包无需再加长度。
|
||||
|
||||
也就是说,v1不再是一个与底层连接无关的协议,它首先强制tls(只是口头强制,唯一区别就是v2ray官方配置文件中不用再写 security: "none" 了,因为注明了v1的话就已经默认了),然后还要判断是否有ws或者grpc这种高级协议的存在,对于这些进行判断,就可以进行优化。
|
||||
也就是说,v1不再是一个与底层连接无关的协议,判断是否有ws或者grpc这种高级协议的存在,对于这些进行判断,就可以进行优化。
|
||||
|
||||
虽然udp加长度只是加了两字节,但这只是write部分,read部分的话,为了防止粘包,就加了buffer,确实是多了一层内存读写操作,所以能精简就要精简,毕竟视频直播视频聊天等这种使用udp的部分都是 流量大户。总之这种优化能视udp的使用用途有不同程度的性能提升
|
||||
|
||||
@@ -50,13 +50,13 @@ v1主要还是 隔离信道的 udp over tcp 的 fullcone 的实现属于重要
|
||||
|
||||
总之,还是要通过升级vless到1版本来实现。
|
||||
|
||||
具体的话,也不需要新cmd,直接在 CmdUDP时,使用不同的数据包格式即可,可以参考socks5和trojan的格式标准
|
||||
具体的话,也不一定需要新cmd,直接在 CmdUDP时,使用不同的数据包格式即可,可以参考socks5和trojan的格式标准
|
||||
|
||||
比如trojan的: https://trojan-gfw.github.io/trojan/protocol
|
||||
|
||||
## udp fullcone的信息传输过程
|
||||
|
||||
tcp代理是不需要fullcone的(也不可能实现),而因为udp的特殊性质,可能需要fullcone
|
||||
tcp代理是不需要fullcone的,也不可能实现;而因为udp的特殊性质,可能需要fullcone
|
||||
|
||||
先观察正常的tcp代理请求,发送一个tcp请求链接到代理服务器,然后直接就会使用这个连接传输双向的数据,这是因为目标是单一的。
|
||||
|
||||
@@ -73,7 +73,7 @@ tcp代理是不需要fullcone的(也不可能实现),而因为udp的特殊
|
||||
|
||||
所以,我们在vless v1版本中,除了并发的udp fullcone实现,也是要实现多路复用的,这样兼顾游戏与视频;
|
||||
|
||||
为了避免抄袭嫌疑,我当然不会重新使用mux协议来实现多路复用,而是自己设计一个传输协议。
|
||||
为了避免抄袭嫌疑,我当然不会重新使用mux协议来实现多路复用,而是自己设计一个传输协议。不过为了兼容现有客户端,我早晚还是要添加mux的支持,再说。
|
||||
|
||||
|
||||
实际上,我还在思索,为什么vless一定要放到tcp上呢,为什么一定要udp over tcp呢?不能仿照socks5,直接在udp上传呢?也许是为了防探测吧,毕竟正常网页浏览的流量都是tcp的。但是实际上,完全可以伪装成 迅雷下载或者微信视频流这种。也有人说可能是怕udp限流、QOS等,所以才出现的hysteria吧。
|
||||
@@ -110,7 +110,7 @@ xray的 vless的mux的fullcone的办法是,在vless里包一个mux协议,然
|
||||
|
||||
那个单独的用于向客户端发送 “新远程地址链接” 的信道,是提前由 客户端主动向代理服务端建立好的一条信道。
|
||||
|
||||
暂且称为 “未知udp地址的信息的接收信道” "a channel that receives udp messages from unknown remote source"
|
||||
暂且称为 “未知udp地址的信息的接收信道”, "a channel that receives udp messages from unknown remote source"
|
||||
|
||||
我这里简称 "CRUMFURS"(信道). 该特殊信息称为 "UMFURS" (信息)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user