Files
v2ray_simple/vless_v1.md
hahahrfool 009162cf40 first
2022-03-09 21:27:13 +08:00

9.2 KiB
Raw Blame History

vless v1

具体我的探讨还可以查看 https://github.com/v2fly/v2ray-core/discussions/1655

总的来说 vless v1 简化了一些流程, 约定永远使用tls与trojan相同,并重点考虑 非多路复用的 udp over tcp的 fullcone实现

关于udp over tcp 的 vless 的 fullcone

只要将目标地址放到一个map里udp建立过的连接 也放到一个map里。然后第二次访问时查看map看有没有用过的有的话就使用之前的连接

然而,这只实现了一半,只记录旧连接是不够的,因为需要把目标的地址也附带发送到客户端,不然的话,客户端是无法区分到底是谁发来的。

这就是udp和tcp的不同。tcp可以确保目标总是自己拨号的那个而udp则是可以任意人都向你发信息

总之还是要通过升级vless到1版本来实现。

具体的话也不需要新cmd直接在 CmdUDP时使用不同的数据包格式即可可以参考socks5和trojan的格式标准

比如trojan的 https://trojan-gfw.github.io/trojan/protocol

udp fullcone的信息传输过程

tcp代理是不需要fullcone的也不可能实现而因为udp的特殊性质可能需要fullcone

先观察正常的tcp代理请求发送一个tcp请求链接到代理服务器然后直接就会使用这个连接传输双向的数据这是因为目标是单一的。

而udpfullcone的代理请求是较为复杂的尽管客户端只是发送了到 一个特定的远程 udp地址的 数据请求但是因为要实现fullcone 代理服务器所传来的信息不一定仅仅是 客户端第一次请求的 那个远程地址 所传来的,而可能是新的其它 地址所传来的参考NAT打洞 然后,在客户端接到这个新地址所发来的数据后, 客户端可能还会接着向这个新地址发送数据,而这时,代理服务器 需要能够判别,这个新地址是不是之前接受过信息的地址,如果是的话,则依然使用之前 代理服务器所使用的 端口进行发送如果不是的话那说明这个是无效信息作为fullcone某个端口A 只能向自己 第一个发送信息的 目的地址,以及 所有 曾经向 A 发送过来信息 的 远程地址 发送信息)

所以随着udp代理的请求的时间推移可发送信息的 远程地址会 呈增加的趋势也就是说建立了与多个远程udp地址的 p2p链接。

如果是xray那种使用mux的办法就是复用一个相同的tcp链接 去传输 多个连续的可以目标不同的 udp连接。

如果是我们要实现的新方法则不是mux即 每一次udp请求 都要产生一个 tcp连接然后进行handshake。

看起来反倒比mux复杂但是因为这个是并发的所以虽然延迟可能高一些但是却提高了吞吐量网速能快一些适合一些视频流的情况

所以我们在vless v1版本中除了并发的udp fullcone实现也是要实现多路复用的这样兼顾游戏与视频

为了避免抄袭嫌疑我当然不会重新使用mux协议来实现多路复用而是自己设计一个传输协议。

实际上我还在思索为什么vless一定要放到tcp上呢为什么一定要udp over tcp呢不能仿照socks5直接在udp上传呢也许是为了防探测吧毕竟正常网页浏览的流量都是tcp的。但是实际上完全可以伪装成 迅雷下载或者微信视频流这种。也有人说可能是怕udp限流、QOS等所以才出现的hysteria吧。

好像quic功能就是做这个的.

总之这个纯udp上的实现问题以后再讨论。先把 udp over tcp搞定。

之所以以后需要讨论纯udp的问题可能是因为为了实现fullcone实际情况会与 udp over tcp 有所不同

非多路复用的 udp over tcp的 fullcone实现

xray的 vless的mux的fullcone的办法是在vless里包一个mux协议然后持续使用该tcp连接 来在内部传送多个udp请求

而我们将要在这里实现的办法是通用办法即不复用tcp连接的办法, 即每一个tcp链接里只包含于一个固定的远程地址的通讯。

不过,问题出现在监听上,我们监听的话,是会随时主动向客户端程序发送 远程服务器传来的信息的,这时,客户端提交 请求是 一个请求地址 一个tcp连接是一一对应的但是我们 返回数据的话,是无法主动跟客户端 建立连接的,而且 返回的数据的地址不一定是 和请求所用的 tcp连接 一一对应, 因为有可能出现 新的 客户端不知道 的远程地址 向 客户端 发送信息

这就是和 socks5或trojan的不同socks5和trojan在传udp时实际上都是多路复用使用的是同一条 代理客户端和代理服务端 建立的链接 来传输多个地址可能不同的数据

所以我的想法是使用一种“居中”的办法即不完全模仿socks5 当 代理服务器收到的udp信息的来源是 客户端已知的来源时,直接使用之前 客户端用于向该地址发送请求 的tcp连接 将数据发回 客户端 而 当新udp信息的来源(A)时未知的时,使用一条单独的信道向客户端发送该数据;

然后如果客户端 有新的向这个 A 发送的 数据请求时再向代理服务器发起新的tcp连接然后该tcp连接 会被保存,等未来 该A 有新数据 想发送到客户端时使用这个特定的tcp连接。

这种好处是,如果目标地址 我客户端之前向其 发送过信息则会复用同一条tcp连接专门与这个地址发送信息

既不是多路复用也不是纯每个请求都建立新tcp链接。

可以说是 “隔离信道”。

具体实现

那个单独的用于向客户端发送 “新远程地址链接” 的信道,是提前由 客户端主动向代理服务端建立好的一条信道。

暂且称为 “未知udp地址的信息的接收信道” "a channel that receives udp messages from unknown remote source"

我这里简称 "CRUMFURS"(信道). 该特殊信息称为 "UMFURS" (信息)

这个信道是多路复用的但是仅限于未知udp远程地址发来的信息而且仅限于服务端向客户端发送数据。

在vless从未主动申请过 udp请求时不必 特地向代理服务器请求建立 CRUMFURS, 因为没有udp的需求。

那么,就需要新增一条 命令,比如 Cmd_CRUMFURS

当客户端 发送的vless信息中 使用 Cmd_CRUMFURS 命令后则该链接保持连接keep alive专门用于客户端接受新udp数据。 同时 此时服务端就意识到客户端以后会产生各种udp需求并做好一些相关准备。

然后客户端第一次 申请向 一个远程地址A 发送 udp请求时还是使用普通的 CMD_UDP命令 新建连接,而且 如果客户端期待回复的话则客户端也不关闭这条连接也是keep alive的然后当客户端想要 第二次向 A发送数据时使用这个之前建立好的tcp连接。监听来自A发来的信息 也是通过读取这个 建立好的tcp连接。

CMD_UDP 信息 的数据和v0协议的内容保持不变。

Cmd_CRUMFURS 指令 的信息,到指令这一项之后 就会完毕。理所当然,因为只是发送一条建立信道的指令,不包含其它信息。

服务端接收到 Cmd_CRUMFURS 指令后当然也是要在验证uuid符合之后会返回一字节 CRUMFURS_ESTABLISHED

UMFURS 信息内容1字节atype几字节的IP视ip为ipv4和ipv6. 因为是远程未知udp信息所以肯定不可能是域名2字节的port然后后面直接接 该信息的承载内容。所以这个信息长度至少为87字节头部和至少一字节数据

服务端对 udp 信息的转发 的行为会与正常 symmetric NAT 不同。并不是对不同的 请求的远程目标地址都要 新 dial一遍udp。因为每dial一遍都会 新使用 服务端的一个 新的随机udp端口而我们为了实现fullcone在传输 到 Cmd_CRUMFURS 里出现过的 目标地址A 的数据时,还是要使用最初 代理服务端 接收 A发送来 数据 所使用的 的服务器udp端口

所以,服务端 要对每一个dial产生的 UDPConn进行保存然后 在接到 客户端的 CMD_UDP 请求时,首先查看 下列情况

  1. 以前是否向这个地址拨过号如果拨过直接从保存的列表中拿出来这个UDPConn直接使用它继续发送数据
  2. 如果没拨过号,那这个远程地址是否曾向 保存的 UDPConn 中 其中一个 发送过数据如果能找到这个UDPConn就使用这个
  3. 如果自己没拨过号,也没有任何一个 UDPConn 接收 过 该远程地址的信息,则这个远程地址 属于新地址我们就新Dial 一次。

所以保存的机制就是一个map以地址为 键,以 UDPConn为内容 自己拨过的号所使用的地址A和该拨号产生的 UDPConn U作为一对 存入 map 然后向A传来的任何其它未知地址B也和 U一起作为一对 存入 map

这个行为可以直接参考 direct/client.go 里的 RelayUDP_to_Direct(extractor proxy.UDP_Extractor) 函数。该函数就实现了fullcone

我规定如果一个vless v1客户端 第一次想要发起 udp请求必须先 发送 Cmd_CRUMFURS 命令