diff --git a/README.md b/README.md index 835fd77..d04d9d9 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,6 @@ [9]: https://img.shields.io/github/release/e1732a364fed/v2ray_simple/all.svg?style=flat-square [10]: https://img.shields.io/github/go-mod/go-version/e1732a364fed/v2ray_simple?style=flat-square -### 最新消息 - -最近在开发vsb计划,即verysimple 面板项目,在 https://github.com/e1732a364fed/vsb , 可以看看。 - -telegram channel: https://t.me/+r5hKQKYyeuowMTcx - # verysimple verysimple, 实际上 谐音来自 V2ray Simple (显然只适用于汉语母语者), 意思就是极简. @@ -659,6 +653,8 @@ verysimple 版本 v1.0.3 如果本作作者突然停更,这里允许任何人以 verysimple 作者的名义fork并 接盘。你只要声称自己是原作者,忘记了github和自己邮箱的密码,只好重开,这不就ok了。 +telegram channel: https://t.me/+r5hKQKYyeuowMTcx + # 免责声明与鸣谢 diff --git a/adv_quic.go b/cmd/verysimple/adv_quic.go similarity index 89% rename from adv_quic.go rename to cmd/verysimple/adv_quic.go index 7c2f00e..ecd4198 100644 --- a/adv_quic.go +++ b/cmd/verysimple/adv_quic.go @@ -1,6 +1,6 @@ //go:build !noquic -package v2ray_simple +package main import _ "github.com/e1732a364fed/v2ray_simple/advLayer/quic" diff --git a/cmd/verysimple/gui.go b/cmd/verysimple/gui.go index 85fc7d4..c1dfb7f 100644 --- a/cmd/verysimple/gui.go +++ b/cmd/verysimple/gui.go @@ -297,9 +297,7 @@ func setupUI() { } filesM.AppendItem("Open github").OnClicked(openUrlFunc(weblink)) filesM.AppendItem("Check github releases").OnClicked(openUrlFunc(weblink + "releases")) - qi := filesM.AppendItem("Quit App") - - qi.OnClicked(func(mi *ui.MenuItem, w *ui.Window) { + filesM.AppendItem("Quit App").OnClicked(func(mi *ui.MenuItem, w *ui.Window) { syscall.Kill(syscall.Getpid(), syscall.SIGINT) //退出app }) diff --git a/tproxy_linux.go b/cmd/verysimple/tproxy.go similarity index 51% rename from tproxy_linux.go rename to cmd/verysimple/tproxy.go index b7ba0ce..94daf47 100644 --- a/tproxy_linux.go +++ b/cmd/verysimple/tproxy.go @@ -1,4 +1,7 @@ -package v2ray_simple +//go:build linux || darwin +// +build linux darwin + +package main import ( _ "github.com/e1732a364fed/v2ray_simple/proxy/tproxy" diff --git a/examples/tproxy.client.toml b/examples/tproxy.client.toml index 5d090f2..6a96e8f 100644 --- a/examples/tproxy.client.toml +++ b/examples/tproxy.client.toml @@ -5,7 +5,10 @@ # 我们不像v2ray/xray一样 使用 dokodemo的额外配置来处理tproxy,而是单独使用 tproxy作为一个协议. -# tproxy只支持客户端, 且目前只支持linux系统, 一般用于软路由/树莓派等. +# tproxy只支持客户端, 且目前只支持linux/macos系统 + +# 在macos中,不需要额外参数,但是仅支持ipv4和tcp。而且macos 12/13 系统上的 pfctl有bug,无法配置路由. +# 建议mac上还是用tun的方法. [[listen]] protocol = "tproxy" @@ -16,8 +19,12 @@ port = 12345 # 不用指明network, 只要指明了tproxy, 就会同时自动监听 tcp和 dup. # 但是如果你指明了 network = tcp, 就不会转发udp, 你指明了 network = udp 的话,就不会转发 tcp. -#extra = { auto_iptables = true } # 如果给出了 auto_iptables, 且 【protocol 为 tproxy 的listen】【只有一个】, 则程序会自动在开始监听前配置好iptables,并在程序退出前 清除iptables中被程序改动的部分。 -# auto_iptables 会一字不差地运行 上面 给出的 toutyrater 的教程中的 iptables命令。 +#extra = { auto_iptables = true } +# 如果给出了 auto_iptables, 且 【protocol 为 tproxy 的listen】【只有一个】, +# 则程序会自动配置路由表 +# linux上,程序会自动在开始监听前配置好iptables,并在程序退出前 清除iptables中被程序改动的部分。 + +# linux上, auto_iptables 会一字不差地运行 上面 给出的 toutyrater 的教程中的 iptables命令。 [[dial]] @@ -29,6 +36,6 @@ version = 0 insecure = true utls = true -sockopt.mark = 255 #根据上面 toutyrater 给出的 iptables命令,我们这里要设置 mark为255. 看一下 上面 toutyrater的教程吧! +sockopt.mark = 255 #linux 上, 根据上面 toutyrater 给出的 iptables命令,我们这里要设置 mark为255. 看一下 上面 toutyrater的教程吧! # sockopt.device = "eth32423" # vs还支持 bindToDevice功能,可以指定 某一个网卡 来监听 或者拨号. diff --git a/examples/tun.client.toml b/examples/tun.client.toml index 962c5dd..9ee5be2 100644 --- a/examples/tun.client.toml +++ b/examples/tun.client.toml @@ -39,6 +39,7 @@ protocol = "tun" # 如果 extra.tun_auto_route 给出,vs_gui会试图自动配置路由表. # 此时必须额外给出需要 直连的ip列表, 比如你的 代理服务器的ip地址; 如果不给出, 则不会自动配置路由表 +# 目前的自动配置逻辑 完全仿照上面的路由示例。 extra.tun_auto_route = true extra.tun_auto_route_direct_list = [ "127.0.0.1" ] diff --git a/netLayer/tproxy/common.go b/netLayer/tproxy/common.go index 12d110c..6afbda3 100644 --- a/netLayer/tproxy/common.go +++ b/netLayer/tproxy/common.go @@ -1,7 +1,11 @@ /* Package tproxy listens tproxy and setup corresponding iptables for linux. -透明代理只能用于linux。 +vs的透明代理只能用于linux 和 macos + +原理上,透明代理与tun/tap不同,透明代理直接工作在传输层第四层tcp/udp上,无需解析ip包 + +下面的文档首先探讨了linux的tproxy # About TProxy 关于透明代理 @@ -96,6 +100,35 @@ https://toutyrater.github.io/app/tproxy.html 透明代理与Redir的参考博客: http://ivo-wang.github.io/2018/02/24/ss-redir/ + +# 关于mac上的实现 + +我参考了如下内容 + +https://penglei.github.io/post/transparent_proxy_on_macosx/ +https://docs.mitmproxy.org/stable/howto-transparent/#macos +https://github.com/Dreamacro/clash/issues/745 +https://github.com/shadowsocks/go-shadowsocks2 + +mac上的透明代理使用了pf命令,通过一条命令来将全局流量导向特定端口; +不过只支持ipv4和tcp + +sudo sysctl -w net.inet.ip.forwarding=1 + +创建一个文件,pf.conf + +rdr pass on en0 inet proto tcp to any port {80, 443} -> 127.0.0.1 port 8080 + +sudo pfctl -f pf.conf +sudo pfctl -e + +查看效果 +sudo pfctl -s nat + +如果想停止使用透明代理访问,禁用pf(sudo pfctl -d)或者清空pf规则(sudo pfctl -F all)即可。 + +我试了,mac12.0 ~ mac13 的 pfctl有问题,设路由不好使。参考 +https://github.com/mitmproxy/mitmproxy/issues/4835 */ package tproxy @@ -155,7 +188,7 @@ func (m *Machine) Init() { func (m *Machine) SetIPTable(port int) { if port > 0 { - SetIPTablesByPort(port) + SetRouteByPort(port) m.iptablePort = port } @@ -175,19 +208,19 @@ func (m *Machine) Stop() { tCh := time.After(time.Second) select { case <-tCh: - log.Println("close tproxy listener timeout") + log.Println("tproxy close listener timeout") case <-ch: break } } if m.UDPConn != nil { - log.Println("closing tproxy udp conn") + log.Println("tproxy closing udp conn") m.UDPConn.Close() } if m.iptablePort > 0 { - CleanupIPTables() + CleanupRoutes() } } diff --git a/netLayer/tproxy/placeholders.go b/netLayer/tproxy/placeholders.go index fd94b32..cab1a95 100644 --- a/netLayer/tproxy/placeholders.go +++ b/netLayer/tproxy/placeholders.go @@ -1,14 +1,14 @@ -//go:build !linux +//go:build !(linux || darwin) package tproxy import "github.com/e1732a364fed/v2ray_simple/utils" -//placeholder for non-linux systems, return utils.ErrNotImplemented -func SetIPTablesByPort(port int) error { +// placeholder for unsupported systems, return utils.ErrNotImplemented +func SetRouteByPort(port int) error { return utils.ErrUnImplemented } -//placeholder for non-linux systems -func CleanupIPTables() { +// placeholder for unsupported systems +func CleanupRoutes() { } diff --git a/netLayer/tproxy/route_darwin.go b/netLayer/tproxy/route_darwin.go new file mode 100644 index 0000000..29e4dca --- /dev/null +++ b/netLayer/tproxy/route_darwin.go @@ -0,0 +1,10 @@ +package tproxy + +import "github.com/e1732a364fed/v2ray_simple/utils" + +func SetRouteByPort(port int) error { + return utils.ErrUnImplemented +} + +func CleanupRoutes() { +} diff --git a/netLayer/tproxy/iptables_linux.go b/netLayer/tproxy/route_linux.go similarity index 93% rename from netLayer/tproxy/iptables_linux.go rename to netLayer/tproxy/route_linux.go index a0f1413..e926c4b 100644 --- a/netLayer/tproxy/iptables_linux.go +++ b/netLayer/tproxy/route_linux.go @@ -9,6 +9,8 @@ import ( "go.uber.org/zap" ) +//配置iptables + func execCmd(cmdStr string) (err error) { utils.ZapLogger.Info("tproxy run cmd", zap.String("cmd", cmdStr)) @@ -81,8 +83,8 @@ iptables -t mangle -X V2RAY_MASK` var lastPortSet int -//commands from https://toutyrater.github.io/app/tproxy.html -func SetIPTablesByPort(port int) error { +// commands from https://toutyrater.github.io/app/tproxy.html +func SetRouteByPort(port int) error { cmd1 := exec.Command("iptables", "-V") if err := cmd1.Run(); err != nil { @@ -93,19 +95,19 @@ func SetIPTablesByPort(port int) error { return execCmdList(fmt.Sprintf(toutyRaterIptableCmdList, port, port)) } -//port 12345 +// port 12345 func SetIPTablesByDefault() error { - return SetIPTablesByPort(12345) + return SetRouteByPort(12345) } -//port 12345 +// port 12345 func CleanupIPTablesByDefault() { execCmdList(fmt.Sprintf(iptableRMCmdList, 12345, 12345)) } -//clear iptables set by the last SetIPTablesByPort call -func CleanupIPTables() { +// clear iptables set by the last SetRouteByPort call +func CleanupRoutes() { if lastPortSet != 0 { execCmdList(fmt.Sprintf(iptableRMCmdList, lastPortSet, lastPortSet)) lastPortSet = 0 diff --git a/netLayer/tproxy/tproxy_darwin.go b/netLayer/tproxy/tproxy_darwin.go new file mode 100644 index 0000000..7b24740 --- /dev/null +++ b/netLayer/tproxy/tproxy_darwin.go @@ -0,0 +1,52 @@ +package tproxy + +import ( + "net" + "syscall" + "unsafe" +) + +// https://github.com/shadowsocks/go-shadowsocks2/blob/master/pfutil/pf_darwin.go +func HandshakeTCP(c *net.TCPConn) (*net.TCPAddr, error) { + const ( + PF_INOUT = 0 + PF_IN = 1 + PF_OUT = 2 + IOC_OUT = 0x40000000 + IOC_IN = 0x80000000 + IOC_INOUT = IOC_IN | IOC_OUT + IOCPARM_MASK = 0x1FFF + LEN = 4*16 + 4*4 + 4*1 + // #define _IOC(inout,group,num,len) (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)) + // #define _IOWR(g,n,t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + // #define DIOCNATLOOK _IOWR('D', 23, struct pfioc_natlook) + DIOCNATLOOK = IOC_INOUT | ((LEN & IOCPARM_MASK) << 16) | ('D' << 8) | 23 + ) + fd, err := syscall.Open("/dev/pf", 0, syscall.O_RDONLY) + if err != nil { + return nil, err + } + defer syscall.Close(fd) + nl := struct { // struct pfioc_natlook + saddr, daddr, rsaddr, rdaddr [16]byte + sxport, dxport, rsxport, rdxport [4]byte + af, proto, protoVariant, direction uint8 + }{ + af: syscall.AF_INET, + proto: syscall.IPPROTO_TCP, + direction: PF_OUT, + } + saddr := c.RemoteAddr().(*net.TCPAddr) + daddr := c.LocalAddr().(*net.TCPAddr) + copy(nl.saddr[:], saddr.IP) + copy(nl.daddr[:], daddr.IP) + nl.sxport[0], nl.sxport[1] = byte(saddr.Port>>8), byte(saddr.Port) + nl.dxport[0], nl.dxport[1] = byte(daddr.Port>>8), byte(daddr.Port) + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), DIOCNATLOOK, uintptr(unsafe.Pointer(&nl))); errno != 0 { + return nil, errno + } + var addr net.TCPAddr + addr.IP = nl.rdaddr[:4] + addr.Port = int(nl.rdxport[0])<<8 | int(nl.rdxport[1]) + return &addr, nil +} diff --git a/netLayer/tun/tun.go b/netLayer/tun/tun.go index d4fabea..5a11ed6 100644 --- a/netLayer/tun/tun.go +++ b/netLayer/tun/tun.go @@ -1,3 +1,13 @@ +/* +Packages tun provides utilities for tun. +tun 工作在第三层 IP层上。 + +我们监听tun,从中提取出 tcp/udp 流。 + +我们使用 github.com/eycorsican/go-tun2socks 包 + +这个包在windows上会使用tap。 +*/ package tun import ( @@ -13,6 +23,7 @@ import ( "go.uber.org/zap" ) +// implements netLayer.MsgConn type coreUDPConnAdapter struct { core.UDPConn netLayer.EasyDeadline diff --git a/proxy/tproxy/server_darwin.go b/proxy/tproxy/server_darwin.go new file mode 100644 index 0000000..69f2253 --- /dev/null +++ b/proxy/tproxy/server_darwin.go @@ -0,0 +1,143 @@ +// Package tproxy implements proxy.Server for tproxy. +package tproxy + +import ( + "io" + "net" + "net/url" + "sync" + + "github.com/e1732a364fed/v2ray_simple/netLayer" + "github.com/e1732a364fed/v2ray_simple/netLayer/tproxy" + "github.com/e1732a364fed/v2ray_simple/proxy" + "github.com/e1732a364fed/v2ray_simple/utils" + "go.uber.org/zap" +) + +//在darwin上,代码相当于linux上的精简版本, 不提供udp支持 + +const name = "tproxy" + +func init() { + proxy.RegisterServer(name, &ServerCreator{}) +} + +type ServerCreator struct{ proxy.CreatorCommonStruct } + +func (ServerCreator) URLToListenConf(url *url.URL, lc *proxy.ListenConf, format int) (*proxy.ListenConf, error) { + if lc == nil { + return nil, utils.ErrNilParameter + } + + return lc, nil +} + +func (ServerCreator) NewServer(lc *proxy.ListenConf) (proxy.Server, error) { + + s := &Server{} + if thing := lc.Extra["auto_iptables"]; thing != nil { + if auto, ok := utils.AnyToBool(thing); ok && auto { + s.shouldSetRoute = true + } + } + return s, nil +} + +func (ServerCreator) AfterCommonConfServer(ps proxy.Server) (err error) { + s := ps.(*Server) + + if s.shouldSetRoute { + err = tproxy.SetRouteByPort(s.ListenConf.Port) + } + return +} + +// implements proxy.ListenerServer +type Server struct { + proxy.Base + + shouldSetRoute bool + + infoChan chan<- netLayer.TCPRequestInfo + tm *tproxy.Machine + sync.Once +} + +func NewServer() (proxy.Server, error) { + d := &Server{} + return d, nil +} +func (*Server) Name() string { return name } + +func (s *Server) SelfListen() (is, tcp, udp bool) { + return true, true, true //darwin的tproxy不支持udp,但是如果传入false,则v2ray_simple包会试图自行监听udp。 + //所以暂时的解决方案是欺骗它告诉他我们都监听 +} + +func (s *Server) Close() error { + s.Stop() + return nil +} + +func (s *Server) Stop() { + s.Once.Do(func() { + s.tm.Stop() + + if s.infoChan != nil { + close(s.infoChan) + } + + }) + +} + +func (s *Server) StartListen(infoChan chan<- netLayer.TCPRequestInfo, udpInfoChan chan<- netLayer.UDPRequestInfo) io.Closer { + + tm := new(tproxy.Machine) + _, lt, _ := s.SelfListen() + + if lt { + s.infoChan = infoChan + + lis, err := netLayer.ListenAndAccept("tcp", s.Addr, s.Sockopt, 0, func(conn net.Conn) { + tcpconn := conn.(*net.TCPConn) + ta, err := tproxy.HandshakeTCP(tcpconn) + if err != nil { + if ce := utils.CanLogErr("tproxy HandshakeTCP failed"); ce != nil { + ce.Write(zap.Error(err)) + } + return + } + targetAddr := netLayer.NewAddrFromTCPAddr(ta) + + info := netLayer.TCPRequestInfo{ + Conn: tcpconn, + Target: targetAddr, + } + + if ce := utils.CanLogInfo("TProxy got new tcp"); ce != nil { + ce.Write(zap.String("->", targetAddr.String())) + } + if tm.Closed() { + return + } + infoChan <- info + }) + if err != nil { + if ce := utils.CanLogErr("TProxy listen tcp failed"); ce != nil { + ce.Write(zap.Error(err)) + } + } + tm.Listener = lis + + } + + tm.Init() + s.tm = tm + + return s +} + +func (s *Server) Handshake(underlay net.Conn) (net.Conn, netLayer.MsgConn, netLayer.Addr, error) { + return nil, nil, netLayer.Addr{}, utils.ErrUnImplemented +}