diff --git a/configs.go b/configs.go index 8dbbcca..68ccb5b 100644 --- a/configs.go +++ b/configs.go @@ -96,8 +96,7 @@ func loadCommonComponentsFromStandardConf() { } } -// set conf variable, or exit the program; 还会设置mainFallback -// 先检查configFileName是否存在,存在就尝试加载文件,否则尝试 -L参数 +// 先检查configFileName是否存在,存在就尝试加载文件到 standardConf 或者 simpleConf,否则尝试 -L参数 func loadConfig() (err error) { fpath := utils.GetFilePath(configFileName) diff --git a/examples/vless_tproxy.client.toml b/examples/vless_tproxy.client.toml new file mode 100644 index 0000000..673524e --- /dev/null +++ b/examples/vless_tproxy.client.toml @@ -0,0 +1,27 @@ +# 本文件使用tproxy作为客户端监听协议. 服务端配置文件 直接使用 vlesss.server.toml 即可 + +# 相关配置和讲解 可以对照 https://toutyrater.github.io/app/tproxy.html +# 比如如何配置iptables, 要好好看上面教程. 原理都是一样的, 只是配置文件略有区别。 + +# 我们不像v2ray/xray一样 使用 dokodemo的额外配置来处理tproxy,而是单独使用 tproxy作为一个协议. + +# tproxy无法在 交互模式/apiServer中配置 或 热加载, 只能用 标准toml配置文件启用. + +[[listen]] +protocol = "tproxy" # tproxy只支持客户端, 且只支持linux系统, 一般用于软路由/树莓派等. +host = "127.0.0.1" +port = 12345 +# sockopt.tproxy = true # 不需要指明 tproxy这个 sockopt, 因为protocol指出trpxoxy后就会自动配置该项. + +# 不用指明network, 只要指明了tproxy, 就会同时自动监听 tcp和 dup. + +[[dial]] +protocol = "vlesss" +uuid = "a684455c-b14f-11ea-bf0d-42010aaa0003" +host = "127.0.0.1" +port = 4434 +version = 0 +insecure = true +utls = true + +sockopt.mark = 255 diff --git a/main.go b/main.go index 5120103..fa3721f 100644 --- a/main.go +++ b/main.go @@ -81,6 +81,8 @@ var ( startPProf bool startMProf bool + + tproxyListenCount int ) func init() { @@ -186,6 +188,10 @@ func main() { for _, serverConf := range standardConf.Listen { thisConf := serverConf + if thisConf.Protocol == "tproxy" { + listenTproxy(thisConf.GetAddrStrForListenOrDial()) + continue + } if thisConf.Uuid == "" && default_uuid != "" { thisConf.Uuid = default_uuid @@ -253,7 +259,7 @@ func main() { configFileQualifiedToRun := false - if (defaultInServer != nil || len(allServers) > 0) && defaultOutClient != nil { + if (defaultInServer != nil || len(allServers) > 0 || tproxyListenCount != 0) && (defaultOutClient != nil) { configFileQualifiedToRun = true if confMode == simpleMode { @@ -902,7 +908,10 @@ func passToOutClient(iics incomingInserverConnState, isfallback bool, wlc net.Co if wlc == nil && udp_wlc == nil { //无wlc证明 inServer 握手失败,且 没有任何回落可用, 直接退出。 utils.Debug("invalid request and no matched fallback, hung up") - iics.wrappedConn.Close() + if iics.wrappedConn != nil { + iics.wrappedConn.Close() + + } return } @@ -939,11 +948,13 @@ func passToOutClient(iics incomingInserverConnState, isfallback bool, wlc net.Co inServer := iics.inServer //尝试分流, 获取到真正要发向 的 outClient - if !iics.forbidDNS_orRoute && routePolicy != nil && !inServer.CantRoute() { + if !iics.forbidDNS_orRoute && routePolicy != nil && !(inServer != nil && inServer.CantRoute()) { desc := &netLayer.TargetDescription{ Addr: targetAddr, - Tag: inServer.GetTag(), + } + if inServer != nil { + desc.Tag = inServer.GetTag() } if ce := utils.CanLogDebug("try routing"); ce != nil { @@ -1027,16 +1038,19 @@ func passToOutClient(iics incomingInserverConnState, isfallback bool, wlc net.Co if targetAddr.IsUDP() { - switch inServer.Name() { - case "socks5": - // UDP Associate: - // 因为socks5的 UDP Associate 办法是较为特殊的,不使用现有tcp而是新建立udp,所以此时该tcp连接已经没用了 - // 但是根据socks5标准,这个tcp链接同样是 keep alive的,否则客户端就会认为服务端挂掉了. - // 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate,并不包含实际地址信息 - default: - iics.shouldCloseInSerBaseConnWhenFinish = true + if inServer != nil { + switch inServer.Name() { + case "socks5": + // UDP Associate: + // 因为socks5的 UDP Associate 办法是较为特殊的,不使用现有tcp而是新建立udp,所以此时该tcp连接已经没用了 + // 但是根据socks5标准,这个tcp链接同样是 keep alive的,否则客户端就会认为服务端挂掉了. + // 另外,此时 targetAddr.IsUDP 只是用于告知此链接是udp Associate,并不包含实际地址信息 + default: + iics.shouldCloseInSerBaseConnWhenFinish = true + } } + } else { //lazy_encrypt情况比较特殊,基础连接何时被关闭会在tlslazy相关代码中处理。 diff --git a/netLayer/dial.go b/netLayer/dial.go index 86d6dfd..613deb1 100644 --- a/netLayer/dial.go +++ b/netLayer/dial.go @@ -3,6 +3,8 @@ package netLayer import ( "crypto/tls" "net" + "syscall" + "time" ) func (addr *Addr) Dial() (net.Conn, error) { @@ -90,3 +92,27 @@ dialedPart: return resultConn, err } + +func (addr Addr) DialWithOpt(sockopt *Sockopt) (net.Conn, error) { + + dialer := &net.Dialer{ + Timeout: time.Second * 16, + } + dialer.Control = func(network, address string, c syscall.RawConn) error { + return c.Control(func(fd uintptr) { + if sockopt != nil { + + if sockopt.Somark != 0 { + SetSomark(int(fd), sockopt.Somark) + } + + if sockopt.TProxy { + SetTproxy(int(fd)) + } + } + }) + } + + return dialer.Dial(addr.Network, addr.String()) + +} diff --git a/netLayer/netlayer.go b/netLayer/netlayer.go index 567ac6f..63eb2b0 100644 --- a/netLayer/netlayer.go +++ b/netLayer/netlayer.go @@ -13,7 +13,6 @@ import ( "io" "log" "net" - "os" "reflect" "syscall" @@ -129,19 +128,3 @@ func (c *ReadWrapper) WriteBuffers(buffers [][]byte) (int64, error) { return int64(n), e } - -//用于 listen和 dial 配置一些底层参数. -type Sockopt struct { - TProxy bool `toml:"tproxy"` - Somark int `toml:"mark"` -} - -type ListenerWithFile interface { - net.Listener - File() (f *os.File, err error) -} - -type ConnWithFile interface { - net.Conn - File() (f *os.File, err error) -} diff --git a/netLayer/sockopt.go b/netLayer/sockopt.go new file mode 100644 index 0000000..6edf099 --- /dev/null +++ b/netLayer/sockopt.go @@ -0,0 +1,22 @@ +package netLayer + +import ( + "net" + "os" +) + +//用于 listen和 dial 配置一些底层参数. +type Sockopt struct { + TProxy bool `toml:"tproxy"` + Somark int `toml:"mark"` +} + +type ListenerWithFile interface { + net.Listener + File() (f *os.File, err error) +} + +type ConnWithFile interface { + net.Conn + File() (f *os.File, err error) +} diff --git a/netLayer/sockopt_linux.go b/netLayer/sockopt_linux.go index 0ac6510..3f6d1f7 100644 --- a/netLayer/sockopt_linux.go +++ b/netLayer/sockopt_linux.go @@ -4,6 +4,14 @@ import ( "syscall" ) +func SetTproxy(fd int) error { + return syscall.SetsockoptInt(fd, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) +} + +func SetSomark(fd int, somark int) error { + return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, somark) +} + func SetTproxyFor(tcplistener ListenerWithFile) error { fileDescriptorSource, err := tcplistener.File() if err != nil { @@ -11,7 +19,7 @@ func SetTproxyFor(tcplistener ListenerWithFile) error { } defer fileDescriptorSource.Close() - return syscall.SetsockoptInt(int(fileDescriptorSource.Fd()), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1) + return SetTproxy(int(fileDescriptorSource.Fd())) } func SetSomarkForListener(tcplistener ListenerWithFile, somark int) error { @@ -21,15 +29,15 @@ func SetSomarkForListener(tcplistener ListenerWithFile, somark int) error { } defer fileDescriptorSource.Close() - return syscall.SetsockoptInt(int(fileDescriptorSource.Fd()), syscall.SOL_SOCKET, syscall.SO_MARK, somark) + return SetSomark(int(fileDescriptorSource.Fd()), somark) } -func SetSomarkForConn(c ConnWithFile) error { +func SetSomarkForConn(c ConnWithFile, somark int) error { fileDescriptorSource, err := c.File() if err != nil { return err } defer fileDescriptorSource.Close() - return syscall.SetsockoptInt(int(fileDescriptorSource.Fd()), syscall.SOL_SOCKET, syscall.SO_MARK, int(config.Mark)) + return SetSomark(int(fileDescriptorSource.Fd()), somark) } diff --git a/netLayer/sockopt_other.go b/netLayer/sockopt_other.go index 8a64462..baaff63 100644 --- a/netLayer/sockopt_other.go +++ b/netLayer/sockopt_other.go @@ -14,3 +14,11 @@ func SetSomarkForListener(tcplistener ListenerWithFile) error { func SetSomarkForConn(c ConnWithFile) error { return nil } + +func SetTproxy(fd int) error { + return nil +} + +func SetSomark(fd int, somark int) error { + return nil +} diff --git a/netLayer/tproxy/tproxy_linux.go b/netLayer/tproxy/tproxy_linux.go index e5242fb..f70cc88 100644 --- a/netLayer/tproxy/tproxy_linux.go +++ b/netLayer/tproxy/tproxy_linux.go @@ -47,23 +47,23 @@ import ( "github.com/hahahrfool/v2ray_simple/utils" ) -func HandshakeTCP(tcpConn *net.TCPConn) (net.Conn, netLayer.Addr, error) { +func HandshakeTCP(tcpConn *net.TCPConn) netLayer.Addr { targetTCPAddr := tcpConn.LocalAddr().(*net.TCPAddr) - return tcpConn, netLayer.Addr{ + return netLayer.Addr{ IP: targetTCPAddr.IP, Port: targetTCPAddr.Port, - }, nil + } } var udpMsgConnMap = make(map[netLayer.HashableAddr]*MsgConn) -func HandshakeUDP(underlay *net.UDPConn) (netLayer.MsgConn, error) { +func HandshakeUDP(underlay *net.UDPConn) (netLayer.MsgConn, netLayer.Addr, error) { bs := utils.GetPacket() n, src, dst, err := ReadFromUDP(underlay, bs) if err != nil { - return nil, err + return nil, netLayer.Addr{}, err } ad := netLayer.NewAddrFromUDPAddr(src) hash := ad.GetHashable() @@ -80,7 +80,7 @@ func HandshakeUDP(underlay *net.UDPConn) (netLayer.MsgConn, error) { conn.readChan <- netLayer.AddrData{Data: bs[:n], Addr: netLayer.NewAddrFromUDPAddr(dst)} - return conn, nil + return conn, netLayer.NewAddrFromUDPAddr(dst), nil } //implements netLayer.MsgConn diff --git a/tproxy_linux.go b/tproxy_linux.go new file mode 100644 index 0000000..6089a89 --- /dev/null +++ b/tproxy_linux.go @@ -0,0 +1,78 @@ +package main + +import ( + "go.uber.org/zap" + "net" + + "github.com/hahahrfool/v2ray_simple/netLayer" + "github.com/hahahrfool/v2ray_simple/netLayer/tproxy" + "github.com/hahahrfool/v2ray_simple/utils" +) + +func listenTproxy(addr string) { + utils.Info("Start running Tproxy ") + + ad, err := netLayer.NewAddr(addr) + if err != nil { + panic(err) + } + var tp TProxy = TProxy(ad) + go tp.StartLoopTCP() + go tp.StartLoopUDP() + + tproxyListenCount++ +} + +var tproxyList []TProxy + +//tproxy因为比较特殊, 不属于 proxy.Server, 需要独特的转发过程去处理. +type TProxy netLayer.Addr + +func (tp TProxy) StartLoop() { + tp.StartLoopTCP() +} + +func (tp TProxy) StartLoopTCP() { + ad := netLayer.Addr(tp) + netLayer.ListenAndAccept("tcp", ad.String(), &netLayer.Sockopt{TProxy: true}, func(conn net.Conn) { + tcpconn := conn.(*net.TCPConn) + targetAddr := tproxy.HandshakeTCP(tcpconn) + + if ce := utils.CanLogErr("TProxy loop read got new tcp"); ce != nil { + ce.Write(zap.String("->", targetAddr.String())) + } + + passToOutClient(incomingInserverConnState{ + wrappedConn: tcpconn, + }, false, tcpconn, nil, targetAddr) + }) + +} + +func (tp TProxy) StartLoopUDP() { + ad := netLayer.Addr(tp) + ad.Network = "udp" + conn, err := ad.DialWithOpt(&netLayer.Sockopt{TProxy: true}) + if err != nil { + if ce := utils.CanLogErr("TProxy StartLoopUDP DialWithOpt failed"); ce != nil { + ce.Write(zap.Error(err)) + } + return + } + udpConn := conn.(*net.UDPConn) + for { + msgConn, raddr, err := tproxy.HandshakeUDP(udpConn) + if err != nil { + if ce := utils.CanLogErr("TProxy StartLoopUDP loop read failed"); ce != nil { + ce.Write(zap.Error(err)) + } + break + } else { + if ce := utils.CanLogErr("TProxy loop read got new udp"); ce != nil { + ce.Write(zap.String("->", raddr.String())) + } + } + + go passToOutClient(incomingInserverConnState{}, false, nil, msgConn, raddr) + } +} diff --git a/tproxy_other.go b/tproxy_other.go new file mode 100644 index 0000000..0587e5b --- /dev/null +++ b/tproxy_other.go @@ -0,0 +1,12 @@ +//go:build !linux +// +build !linux + +package main + +import ( + "github.com/hahahrfool/v2ray_simple/utils" +) + +func listenTproxy(addr string) { + utils.Warn("Tproxy not possible on non-linux device") +} diff --git a/udp.go b/udp.go deleted file mode 100644 index 06ab7d0..0000000 --- a/udp.go +++ /dev/null @@ -1 +0,0 @@ -package main