Files
v2ray_simple/netLayer/udp_relay.go
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

281 lines
6.5 KiB
Go
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.

package netLayer
import (
"net"
"sync"
"time"
"github.com/hahahrfool/v2ray_simple/utils"
)
const (
MaxUDP_packetLen = 64 * 1024 // 关于 udp包数据长度可参考 https://cloud.tencent.com/developer/article/1021196
)
var (
//udp不能无限监听, 否则每一个udp申请都对应打开了一个本地udp端口一直监听的话时间一长就会导致 too many open files
// 因为实际上udp在网页代理中主要用于dns请求, 所以不妨设的小一点。
// 放心,只要能持续不断地从远程服务器收到数据, 建立的udp连接就会持续地更新Deadline 而续命一段时间.
UDP_timeout = time.Minute * 3
)
//本文件内含 一些 转发 udp 数据的 接口与方法
//MsgConn一般用于 udp. 是一种类似 net.PacketConn 的包装
//
//使用Addr是因为有可能申请的是域名而不是ip
type MsgConn interface {
ReadFrom() ([]byte, Addr, error)
WriteTo([]byte, Addr) error
CloseConnWithRaddr(raddr Addr) error //关闭特定连接
Close() error //关闭所有连接
Fullcone() bool //若Fullcone, 则在转发因另一端关闭而结束后, RelayUDP函数不会Close它.
}
// 阻塞. 返回从 rc 下载的总字节数. 拷贝完成后自动关闭双端连接.
func RelayUDP(rc, lc MsgConn) int {
//在转发时, 有可能有多种情况
/*
1. dokodemo 监听udp 定向 导向到 direct 的远程udp实际地址
此时因为是定向的, 所以肯定不是fullcone
dokodemo 用的是 UniTargetMsgConn, underlay 是 netLayer.UDPConn, 其已经设置了UDP_timeout
在 netLayer.UDPConn 超时后, ReadFrom 就会解放, 并触发双向Close, 来关闭我们的 direct的udp连接。
1.5. 比较少见的情况, dokodemo监听tcp, 然后发送到 direct 的udp. 此时客户应用程序可以手动关闭tcp连接来帮我们触发 udp连接 的 close
2. socks5监听 udp, 导向到 direct 的远程udp实际地址
socks5端只用一个udp连接来监听所有信息, 所以不能关闭, 所以没有设置超时
此时我们需要对 每一个 direct的udp连接 设置超时, 否则就会一直占用端口
3. socks5 监听udp, 导向到 trojan, 然后 服务端的 trojan 再导向 direct
trojan 也是用一个信道来接收udp的所有请求的, 所以trojan的连接也不能关.
所以依然需要在服务端 的 direct上面 加Read 时限
否则 rc.ReadFrom() 会卡住而不返回.
因为direct 使用 UDPMsgConnWrapper而我们已经在 UDPMsgConnWrapper里加了这个逻辑, 所以可以放心了.
4. fullcone, 此时不能对整个监听端口进行close会影响其它外部链接发来的连接。
*/
go func() {
for {
bs, raddr, err := lc.ReadFrom()
if err != nil {
break
}
err = rc.WriteTo(bs, raddr)
if err != nil {
break
}
}
if !rc.Fullcone() {
rc.Close()
}
if !lc.Fullcone() {
lc.Close()
}
}()
count := 0
for {
bs, raddr, err := rc.ReadFrom()
if err != nil {
break
}
err = lc.WriteTo(bs, raddr)
if err != nil {
break
}
count += len(bs)
}
if !rc.Fullcone() {
rc.Close()
}
if !lc.Fullcone() {
lc.Close()
}
return count
}
// symmetric, proxy/dokodemo 有用到.
type UniTargetMsgConn struct {
net.Conn
target Addr
}
func (u UniTargetMsgConn) Fullcone() bool {
return false
}
func (u UniTargetMsgConn) ReadFrom() ([]byte, Addr, error) {
bs := utils.GetPacket()
n, err := u.Conn.Read(bs)
if err != nil {
return nil, Addr{}, err
}
return bs[:n], u.target, err
}
func (u UniTargetMsgConn) WriteTo(bs []byte, _ Addr) error {
_, err := u.Conn.Write(bs)
return err
}
func (u UniTargetMsgConn) CloseConnWithRaddr(raddr Addr) error {
return u.Conn.Close()
}
func (u UniTargetMsgConn) Close() error {
return u.Conn.Close()
}
//可满足fullcone, 由 Fullcone 的值决定. 在proxy/direct 被用到.
//
type UDPMsgConnWrapper struct {
conn *net.UDPConn
IsServer bool
fullcone bool
symmetricMap map[HashableAddr]*net.UDPConn
symmetricMapMutex sync.RWMutex
}
//使用传入的laddr监听udp; 若未给出laddr, 使用一个随机端口监听
func NewUDPMsgConnClientWrapper(laddr *net.UDPAddr, fullcone bool, isserver bool) *UDPMsgConnWrapper {
uc := new(UDPMsgConnWrapper)
//if laddr == nil {
// laddr, _ = net.ResolveUDPAddr("udp", ":"+RandPortStr())
//}
udpConn, _ := net.ListenUDP("udp", laddr)
uc.conn = udpConn
uc.fullcone = fullcone
uc.IsServer = isserver
if !fullcone {
uc.symmetricMap = make(map[HashableAddr]*net.UDPConn)
}
return uc
}
func (u *UDPMsgConnWrapper) Fullcone() bool {
return u.fullcone
}
func (u *UDPMsgConnWrapper) ReadFrom() ([]byte, Addr, error) {
bs := utils.GetPacket()
if !u.fullcone {
//如果不是fullcone, 则我们需要限时关闭
u.conn.SetReadDeadline(time.Now().Add(UDP_timeout))
}
n, ad, err := u.conn.ReadFromUDP(bs)
if err != nil {
return nil, Addr{}, err
}
if !u.fullcone {
//既然读到了, 那么就取消限时
u.conn.SetReadDeadline(time.Time{})
}
return bs[:n], NewAddrFromUDPAddr(ad), nil
}
func (u *UDPMsgConnWrapper) WriteTo(bs []byte, raddr Addr) error {
if !u.fullcone && !u.IsServer {
//非fullcone时, 强制 symmetryc, 对每个远程地址 都使用一个 对应的新laddr
thishash := raddr.GetHashable()
if len(u.symmetricMap) == 0 {
_, err := u.conn.WriteTo(bs, raddr.ToUDPAddr())
if err == nil {
u.symmetricMapMutex.Lock()
u.symmetricMap[thishash] = u.conn
u.symmetricMapMutex.Unlock()
}
return err
}
u.symmetricMapMutex.RLock()
theConn := u.symmetricMap[thishash]
u.symmetricMapMutex.RUnlock()
if theConn == nil {
var e error
theConn, e = net.ListenUDP("udp", nil)
if e != nil {
return e
}
u.symmetricMapMutex.Lock()
u.symmetricMap[thishash] = theConn
u.symmetricMapMutex.Unlock()
}
_, err := theConn.WriteTo(bs, raddr.ToUDPAddr())
return err
} else {
_, err := u.conn.WriteTo(bs, raddr.ToUDPAddr())
return err
}
}
func (u *UDPMsgConnWrapper) CloseConnWithRaddr(raddr Addr) error {
if !u.IsServer {
if u.fullcone {
u.conn.SetReadDeadline(time.Now())
} else {
u.symmetricMapMutex.Lock()
thehash := raddr.GetHashable()
theConn := u.symmetricMap[thehash]
if theConn != nil {
delete(u.symmetricMap, thehash)
theConn.Close()
}
u.symmetricMapMutex.Unlock()
}
}
return nil
}
func (u *UDPMsgConnWrapper) Close() error {
if !u.IsServer && u.fullcone {
//Close一般只用于关闭客户端、非fullcone的情况, 因为只有这种情况下,才会有 一个 u仅与一个 raddr对话 的清醒.
return u.conn.Close()
} else {
return nil
}
}