防止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:
hahahrfool
2022-03-29 19:00:14 +08:00
parent 9e3265817a
commit f2adcdcd45
21 changed files with 505 additions and 137 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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
View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
View 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
}

View File

@@ -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
View 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))
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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),

View File

@@ -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)

View File

@@ -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
//

View File

@@ -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

View File

@@ -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" (信息)