Files
v2ray_simple/backup
hahahrfool ce735dbb99 修订udp代码; dial配置 添加 fullcone 选项;默认为非fullcone
现在整个程序均通过了go test, main 也可以正常运行了。

Relay_UDP 函数添加流量计数;

发现之前 Relay函数的流量计数 在main.go里参数传反了,导致实际上计数的是上传而不是下载,已修复

对fullcone的情况做了特别考量。MsgConn的 Close函数在fullcone时不能随便被调用。

因此我添加了一个 CloseConnWithRaddr(raddr Addr) error  方法,以及 Fullcone() bool     方法

在utils包的init部分使用 rand 随机种子
2022-04-08 20:31:59 +08:00

439 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 下面一段代码 单独处理 udp承载数据的特殊转发。
//
// 这里只处理 vless v1 的CRUMFURS 转发到direct的情况 以及 socks5 的udp associate 转发 的情况;
// 如果条件不符合则会跳过这段代码 并进入下一阶段
if targetAddr.IsUDP() {
}
switch inServer.Name() {
case "vless":
if client.Name() == "direct" {
uc := wlc.(*vless.UserTCPConn)
if uc.GetProtocolVersion() < 1 {
break
}
// 根据 vless_v1的讨论vless_v1 的udp转发的 通信方式 也是与tcp连接类似的分离信道方式
// 上面已经把 CRUMFURS 的情况过滤掉了所以现在这里就是普通的udp请求
//
// 因为direct使用 proxy.RelayUDP_to_Direct 函数 直接实现了fullcone
// 那么我们只需要传入一个 UDP_Extractor 即可
// 我们通过 netLayer.UniUDP_Extractor 达到此目的
//unknownRemoteAddrMsgWriter 在 vless v1中的实现就是 theCRUMFURS vless v0就是mux
id := uc.GetIdentityStr()
vlessServer := inServer.(*vless.Server)
theCRUMFURS := vlessServer.Get_CRUMFURS(id)
var unknownRemoteAddrMsgWriter netLayer.UDPResponseWriter
unknownRemoteAddrMsgWriter = theCRUMFURS
uniExtractor := netLayer.NewUniUDP_Extractor(*targetAddr.ToUDPAddr(), wlc, unknownRemoteAddrMsgWriter)
netLayer.RelayUDP_to_Direct(uniExtractor) //阻塞
return
}
case "socks5":
// 此时socks5包已经帮我们dial好了一个udp连接即wlc但是还未读取到客户端想要访问的东西
udpConn := wlc.(*socks5.UDPConn)
dialFunc := func(targetAddr netLayer.Addr) (io.ReadWriter, error) {
rw, ne := dialClient(incomingInserverConnState{}, targetAddr, client, false, nil, true)
if ne != (utils.NumErr{}) {
return rw, ne
}
return rw, nil
}
if putter, ok := client.(netLayer.UDP_Putter); ok {
// 将 outClient 视为 UDP_Putter 就可以转发udp信息了
// vless.Client 实现了 UDP_Putter, 新连接的Handshake过程会在 dialFunc 被调用 时发生
//UDP_Putter 不使用传统的Handshake过程因为Handshake是用于第一次数据然后后面接着的双向传输都不再需要额外信息而 UDP_Putter 每一次数据传输都是需要传输 目标地址的,所以每一次都需要一些额外数据,这就是我们 UDP_Putter 接口去解决的事情。
//因为UDP Associate后就会保证以后的向 wlc 的 所有请求数据都是udp请求所以可以在这里直接循环转发了。
go udpConn.StartPushResponse(putter)
udpConn.StartReadRequest(putter, dialFunc)
} else if pc, ok := client.(netLayer.UDP_Putter_Generator); ok {
// direct 实现了 UDP_Putter_Generator
putter := pc.GetNewUDP_Putter()
if putter != nil {
go udpConn.StartPushResponse(putter)
udpConn.StartReadRequest(putter, dialFunc)
}
} else {
if ce := utils.CanLogErr("socks5 udp err"); ce != nil {
ce.Write(
zap.String("detail", "server -> client for udp, but client didn't implement netLayer.UDP_Putter or UDP_Putter_Generator"),
zap.String("client", client.Name()),
)
}
}
return
}
//////////////////// 接口 ////////////////////
type UDPRequestReader interface {
GetNewUDPRequest() (net.UDPAddr, []byte, error)
}
type UDPResponseWriter interface {
WriteUDPResponse(net.UDPAddr, []byte) error
}
// UDP_Extractor, 用于从一个虚拟的协议中提取出 udp请求
//
// 从一个未知协议中读取UDP请求然后试图得到该请求的回应(大概率是直接通过direct发出) 并写回
type UDP_Extractor interface {
UDPRequestReader
UDPResponseWriter
}
// 写入一个UDP请求; 可以包裹成任意协议。
// 因为有时该地址从来没申请过所以此时就要用dialFunc创建一个新连接
type UDPRequestWriter interface {
WriteUDPRequest(target net.UDPAddr, request []byte, dialFunc func(targetAddr Addr) (io.ReadWriter, error)) error
CloseUDPRequestWriter() //如果read端失败,则一定需要close Write端. CloseUDPRequestWriter就是这个用途.
}
//拉取一个新的 UDP 响应
type UDPResponseReader interface {
GetNewUDPResponse() (net.UDPAddr, []byte, error)
}
// UDP_Putter, 用于把 udp请求转换成 虚拟的协议
//
// 向一个特定的协议 写入 UDP请求然后试图读取 该请求的回应. 比如vless.Client就实现了它
type UDP_Putter interface {
UDPRequestWriter
UDPResponseReader
}
type UDP_Putter_Generator interface {
GetNewUDP_Putter() UDP_Putter
}
//////////////////// 具体实现 ////////////////////
// 最简单的 UDP_Putter用于客户端; 不处理内部数据,直接认为要 发送给服务端的信息 要发送到一个特定的地址
// 如果指定的地址不是 默认的地址,则发送到 unknownRemoteAddrMsgWriter
//
// 对于 vless v1来说, unknownRemoteAddrMsgWriter 要做的 就是 新建一个与服务端的 请求udp的连接
// 然后这个新连接就变成了新的 UniUDP_Putter
type UniUDP_Putter struct {
targetAddr net.UDPAddr
io.ReadWriter
unknownRemoteAddrMsgWriter UDPRequestWriter
}
//
func (e *UniUDP_Putter) GetNewUDPResponse() (net.UDPAddr, []byte, error) {
bs := make([]byte, MaxUDP_packetLen)
n, err := e.ReadWriter.Read(bs)
if err != nil {
return e.targetAddr, nil, err
}
return e.targetAddr, bs[:n], nil
}
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)
return
} else {
if e.unknownRemoteAddrMsgWriter == nil {
return
}
// 普通的 WriteUDPRequest需要调用 dialFunc来拨号新链接而我们这里 直接就传递给 unknownRemoteAddrMsgWriter 了
return e.unknownRemoteAddrMsgWriter.WriteUDPRequest(addr, bs, dialFunc)
}
}
// 最简单的 UDP_Extractor用于服务端; 不处理内部数据,直接认为客户端传来的内部数据的目标为一个特定值。
// 收到的响应数据的来源 如果和 targetAddr 相同的话,直接写入传入的 ReadWriter
// 收到的外界数据的来源 如果和 targetAddr 不同的话那肯定就是使用了fullcone那么要传入 unknownRemoteAddrMsgWriter 如果New时传入unknownRemoteAddrMsgWriter的 是nil的话那么意思就是不支持fullcone将直接舍弃这一部分数据。
type UniUDP_Extractor struct {
targetAddr net.UDPAddr
io.ReadWriter
unknownRemoteAddrMsgWriter UDPResponseWriter
}
// 新建unknownRemoteAddrMsgWriter 用于写入 未知来源响应rw 用于普通的客户请求的目标的响应
func NewUniUDP_Extractor(addr net.UDPAddr, rw io.ReadWriter, unknownRemoteAddrMsgWriter UDPResponseWriter) *UniUDP_Extractor {
return &UniUDP_Extractor{
targetAddr: addr,
ReadWriter: rw,
unknownRemoteAddrMsgWriter: unknownRemoteAddrMsgWriter,
}
}
// 从客户端连接中 提取出 它的 UDP请求就是直接读取数据。然后搭配上之前设置好的地址
func (e *UniUDP_Extractor) GetNewUDPRequest() (net.UDPAddr, []byte, error) {
bs := make([]byte, MaxUDP_packetLen)
n, err := e.ReadWriter.Read(bs)
if err != nil {
return e.targetAddr, nil, err
}
return e.targetAddr, bs[:n], nil
}
// WriteUDPResponse 写入远程服务器的响应;要分情况讨论。
// 因为是单一目标extractor所以正常情况下 传入的response 的源地址 也 应和 e.targetAddr 相同,
// 如果地址不同的话那肯定就是使用了fullcone那么要传入 unknownRemoteAddrMsgWriter
func (e *UniUDP_Extractor) WriteUDPResponse(addr net.UDPAddr, bs []byte) (err error) {
if addr.String() == e.targetAddr.String() {
_, err = e.ReadWriter.Write(bs)
return
} else {
//如果未配置 unknownRemoteAddrMsgWriter 则说明不支持fullcone。这并不是错误而是可选的。看你想不想要fullcone
if e.unknownRemoteAddrMsgWriter == nil {
return
}
return e.unknownRemoteAddrMsgWriter.WriteUDPResponse(addr, bs)
}
}
//一种简单的本地 UDP_Extractor + UDP_Putter
type UDP_Pipe struct {
requestChan, responseChan chan UDPAddrData
requestChanClosed, responseChanClosed bool
}
func (u *UDP_Pipe) IsInvalid() bool {
return u.requestChanClosed || u.responseChanClosed
}
func (u *UDP_Pipe) closeRequestChan() {
if !u.requestChanClosed {
close(u.requestChan)
u.requestChanClosed = true
}
}
func (u *UDP_Pipe) closeResponseChan() {
if !u.responseChanClosed {
close(u.responseChan)
u.responseChanClosed = true
}
}
func (u *UDP_Pipe) Close() {
u.closeRequestChan()
u.closeResponseChan()
}
func NewUDP_Pipe() *UDP_Pipe {
return &UDP_Pipe{
requestChan: make(chan UDPAddrData, 10),
responseChan: make(chan UDPAddrData, 10),
}
}
func (u *UDP_Pipe) CloseUDPRequestWriter() {
u.closeRequestChan()
}
func (u *UDP_Pipe) GetNewUDPRequest() (net.UDPAddr, []byte, error) {
d, ok := <-u.requestChan
if ok {
return d.Addr, d.Data, nil
} else {
//如果requestChan被关闭了就要同时关闭 responseChan
u.closeResponseChan()
return net.UDPAddr{}, nil, io.EOF
}
}
func (u *UDP_Pipe) GetNewUDPResponse() (net.UDPAddr, []byte, error) {
d, ok := <-u.responseChan
if ok {
return d.Addr, d.Data, nil
} else {
//如果 responseChan 被关闭了,就要同时关闭 requestChan
u.closeRequestChan()
return net.UDPAddr{}, nil, io.EOF
}
}
// 会保存bs的副本不必担心数据被改变的问题。
func (u *UDP_Pipe) WriteUDPResponse(addr net.UDPAddr, bs []byte) error {
bsCopy := make([]byte, len(bs))
copy(bsCopy, bs)
u.responseChan <- UDPAddrData{
Addr: addr,
Data: bsCopy,
}
return nil
}
// 会保存bs的副本不必担心数据被改变的问题。
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)
u.requestChan <- UDPAddrData{
Addr: addr,
Data: bsCopy,
}
return nil
}
// RelayUDP_to_Direct 用于 从一个未知协议读取 udp请求然后通过 直接的udp连接 发送到 远程udp 地址。
// 该函数是阻塞的。而且实现了fullcone; 本函数会直接处理 对外新udp 的dial
//
// RelayUDP_to_Direct 与 RelayTCP 函数 的区别是已经建立的udpConn是可以向其它目的地址发送信息的
// 服务端可以向 客户端发送 非客户端发送过数据 的地址 发来的信息
// 原理是,客户端请求第一次后,就会在服务端开放一个端口,然后其它远程主机就会发现这个端口并试图向客户端发送数据
// 而由于fullcone所以如果客户端要请求一个 不同的udp地址的话如果这个udp地址是之前发送来过信息那么就要用之前建立过的udp连接这样才能保证端口一致
//
func RelayUDP_to_Direct(extractor UDP_Extractor) {
type connState struct {
conn *net.UDPConn
raddrMap map[string]bool //所有与thisconn关联的 raddr
}
//具体实现: 每当有对新远程udp地址的请求发生时就会同时 监听 “用于发送该请求到远程udp主机的本地udp端口”接受一切发往 该端口的数据
var dialedUDPConnMap = make(map[string]*connState)
var mutex sync.RWMutex
for {
raddr, requestData, err := extractor.GetNewUDPRequest()
if err != nil {
break
}
first_raddrStr := raddr.String()
mutex.RLock()
oldConn := dialedUDPConnMap[first_raddrStr]
mutex.RUnlock()
if oldConn != nil {
oldConn.conn.Write(requestData)
} else {
newConn, err := net.DialUDP("udp", nil, &raddr)
if err != nil {
break
}
_, err = newConn.Write(requestData)
if err != nil {
break
}
first_cs := &connState{
conn: newConn,
raddrMap: make(map[string]bool),
}
first_cs.raddrMap[first_raddrStr] = true
mutex.Lock()
dialedUDPConnMap[first_raddrStr] = first_cs
mutex.Unlock()
//监听所有发往 newConn的 远程任意主机 发来的消息。
go func(thisconn *net.UDPConn, supposedRemoteAddr net.UDPAddr) {
bs := make([]byte, MaxUDP_packetLen)
for {
thisconn.SetDeadline(time.Now().Add(UDP_timeout))
//log.Println("redirect udp, start read", supposedRemoteAddr)
n, raddr, err := thisconn.ReadFromUDP(bs)
if err != nil {
//timeout后就会删掉第一个拨号的raddr,以及因为fullcone而产生的其它raddr
//然后关闭此udp端口
mutex.Lock()
delete(dialedUDPConnMap, first_raddrStr)
for anotherRaddr := range first_cs.raddrMap {
delete(dialedUDPConnMap, anotherRaddr)
}
mutex.Unlock()
thisconn.Close()
break
}
// 这个远程 地址 无论是新的还是旧的, 都是要 和 newConn关联的下一次向 这个远程地址发消息时,也要用 newConn来发而不是新dial一个。
hasThisRaddr := false
this_raddr_str := raddr.String()
mutex.RLock()
_, hasThisRaddr = dialedUDPConnMap[this_raddr_str]
mutex.RUnlock()
if !hasThisRaddr {
mutex.Lock()
dialedUDPConnMap[this_raddr_str] = first_cs
first_cs.raddrMap[this_raddr_str] = true
mutex.Unlock()
}
//log.Println("redirect udp, will write to extractor", string(bs[:n]))
err = extractor.WriteUDPResponse(*raddr, bs[:n])
if err != nil {
break
}
}
}(newConn, raddr)
}
}
}