From 12a31e1b89a1996a0eaaa06ada7fc763fa59b475 Mon Sep 17 00:00:00 2001 From: e1732a364fed <75717694+e1732a364fed@users.noreply.github.com> Date: Sat, 1 Jan 2000 00:00:00 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E8=AE=A2cmd,gui;=E6=B7=BB=E5=8A=A0gui?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E9=85=8D=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/verysimple/cli.go | 10 +- cmd/verysimple/gui.go | 3 +- cmd/verysimple/gui_conf.go | 2 +- cmd/verysimple/gui_route.go | 198 ++++++++++++++++++++++++++++++++++++ cmd/verysimple/main.go | 7 +- machine/apiServer.go | 2 +- machine/conf.go | 4 +- machine/machine.go | 47 ++++++--- netLayer/route.go | 56 ++++++++-- utils/algo.go | 13 ++- 10 files changed, 304 insertions(+), 38 deletions(-) create mode 100644 cmd/verysimple/gui_route.go diff --git a/cmd/verysimple/cli.go b/cmd/verysimple/cli.go index 0fd8fab..1482fcc 100644 --- a/cmd/verysimple/cli.go +++ b/cmd/verysimple/cli.go @@ -41,7 +41,7 @@ func flist(list []*CliCmd) (result []func()) { var cliCmdList = []*CliCmd{ { "查询当前状态", func() { - mainM.PrintAllState(os.Stdout) + mainM.PrintAllState(os.Stdout, false) }, }, { "打印当前版本所支持的所有协议", printSupportedProtocols, @@ -290,7 +290,7 @@ func interactively_hotRemoveServerOrClient(m *machine.M) { utils.PrintStr("即将开始热删除配置步骤, 删除正在运行的配置可能有未知风险,谨慎操作\n") utils.PrintStr("【当前所有配置】为:\n") utils.PrintStr(delimiter) - m.PrintAllState(os.Stdout) + m.PrintAllState(os.Stdout, false) var items []string if m.ServerCount() > 0 { @@ -388,7 +388,7 @@ func interactively_hotRemoveServerOrClient(m *machine.M) { utils.PrintStr("删除成功!当前状态:\n") utils.PrintStr(delimiter) - m.PrintAllState(os.Stdout) + m.PrintAllState(os.Stdout, false) } func interactively_hotLoadUrlConfig(m *machine.M) { @@ -459,7 +459,7 @@ func interactively_hotLoadConfigFile(m *machine.M) { utils.PrintStr("【注意】我们交互模式只支持热添加listen和dial, 对于dns/route/fallback的热增删, 请期待api server未来的实现.\n") utils.PrintStr("【当前所有配置】为:\n") utils.PrintStr(delimiter) - m.PrintAllState(os.Stdout) + m.PrintAllState(os.Stdout, false) utils.PrintStr("请输入你想添加的文件名称\n") @@ -507,7 +507,7 @@ func interactively_hotLoadConfigFile(m *machine.M) { utils.PrintStr("添加成功!当前状态:\n") utils.PrintStr(delimiter) - m.PrintAllState(os.Stdout) + m.PrintAllState(os.Stdout, false) } func interactively_adjust_loglevel() { diff --git a/cmd/verysimple/gui.go b/cmd/verysimple/gui.go index 67767c8..96325b7 100644 --- a/cmd/verysimple/gui.go +++ b/cmd/verysimple/gui.go @@ -151,7 +151,7 @@ func makeBasicControlsPage() ui.Control { printStateBtn := ui.NewButton("打印当前状态") printStateBtn.OnClicked(func(b *ui.Button) { - mainM.PrintAllStateForHuman(os.Stdout) + mainM.PrintAllStateForHuman(os.Stdout, false) }) toggleHbox.Append(printStateBtn, false) @@ -502,6 +502,7 @@ func setupTab() { tab.Append("基础控制", makeBasicControlsPage()) tab.Append("代理控制", makeConfPage()) + tab.Append("路由控制", makeRoutePage()) tab.Append("app控制", makeAppPage()) //for i := 0; i < tab.NumPages(); i++ { diff --git a/cmd/verysimple/gui_conf.go b/cmd/verysimple/gui_conf.go index 528d9c3..9bfd87b 100644 --- a/cmd/verysimple/gui_conf.go +++ b/cmd/verysimple/gui_conf.go @@ -673,7 +673,7 @@ func addConfControls(sc proxy.StandardConf, vb *ui.Box, isDial bool) { mainM.Start() } - mainM.PrintAllStateForHuman(os.Stdout) + mainM.PrintAllStateForHuman(os.Stdout, false) } } diff --git a/cmd/verysimple/gui_route.go b/cmd/verysimple/gui_route.go new file mode 100644 index 0000000..7685dbd --- /dev/null +++ b/cmd/verysimple/gui_route.go @@ -0,0 +1,198 @@ +//go:build gui + +package main + +import ( + "net/netip" + "os" + "strconv" + + "github.com/e1732a364fed/ui" + "github.com/e1732a364fed/v2ray_simple/netLayer" + "github.com/e1732a364fed/v2ray_simple/utils" + "go.uber.org/zap" +) + +func makeRoutePage() ui.Control { + + vb := ui.NewVerticalBox() + vb.SetPadded(true) + + vb.Append(ui.NewLabel("目前vs_gui只支持路由单项配置,不支持数组; 若要使用数组, 请手动编辑配置文件"), false) + vb.Append(ui.NewLabel("编辑字符串时, 请确保按回车确认. 之后还要点击提交更改. 否则更改不会生效"), false) + + newBtn := ui.NewButton("新增") + rmBtn := ui.NewButton("删除") + submitBtn := ui.NewButton("提交更改") + detailBtn := ui.NewButton("详细说明") + + hb := ui.NewHorizontalBox() + hb.SetPadded(true) + + hb.Append(newBtn, false) + hb.Append(rmBtn, false) + hb.Append(submitBtn, false) + hb.Append(detailBtn, false) + + vb.Append(hb, false) + + rth := newRouteTableHandler() + rp := mainM.GetRoutePolicy() + + if rp != nil { + rpn := rp.Clone() + rth.RoutePolicy = rpn + } + model := ui.NewTableModel(rth) + + curSelectedIdx := -1 + + table := ui.NewTable(&ui.TableParams{ + Model: model, + RowBackgroundColorModelColumn: 3, + }) + table.AppendTextColumn("idx", 0, 0, nil) + table.AppendTextColumn("ToTag", 1, 1, nil) + table.AppendTextColumn("User", 2, 2, nil) + table.AppendTextColumn("IP", 3, 3, nil) + table.AppendTextColumn("Match", 4, 4, nil) + table.AppendTextColumn("Domain", 5, 5, nil) + + table.SetColumnWidth(0, 20) + + table.OnRowClicked(func(t *ui.Table, i int) { + curSelectedIdx = i + }) + + vb.Append(table, false) + + newBtn.OnClicked(func(b *ui.Button) { + rth.List = append(rth.List, netLayer.NewFullRouteSet()) + model.RowInserted(len(rth.List) - 1) + }) + rmBtn.OnClicked(func(b *ui.Button) { + if curSelectedIdx < 0 || curSelectedIdx >= len(rth.List) { + return + } + rth.List = utils.TrimSlice(rth.List, curSelectedIdx) + model.RowDeleted(curSelectedIdx) + }) + submitBtn.OnClicked(func(b *ui.Button) { + rpn := rth.RoutePolicy.Clone() + mainM.SetRoutePolicy(&rpn) + mainM.PrintAllStateForHuman(os.Stdout, true) + }) + + detailBtn.OnClicked(func(b *ui.Button) { + ui.MsgBox(mainwin, + "路由配置详细说明", + ` +match 为域名匹配任意字符串, +domain 匹配域名或子域名 + +每一项在配置文件中都可以提供数组, 而在本gui中只能配置一个.如果你编辑了原来的数组配置, 则原数据不会被保存 + `) + }) + + return vb +} + +type routeTableH struct { + netLayer.RoutePolicy +} + +func newRouteTableHandler() *routeTableH { + m := new(routeTableH) + return m +} + +func (rt *routeTableH) ColumnTypes(m *ui.TableModel) []ui.TableValue { + return []ui.TableValue{ + + ui.TableString(""), + } +} + +func (rt *routeTableH) NumRows(m *ui.TableModel) int { + return len(rt.List) +} + +func (rt *routeTableH) CellValue(m *ui.TableModel, row, column int) ui.TableValue { + + switch column { + case 0: + return ui.TableString(strconv.Itoa(row)) + case 1: + return ui.TableString(rt.List[row].OutTag) + case 2: + us := rt.List[row].Users + if len(us) > 0 { + u := utils.GetOneFromMap(us, "") + return ui.TableString(u) + } + return ui.TableString("") + case 3: + ips := rt.List[row].IPs + if len(ips) > 0 { + addr := utils.GetOneFromMap(ips, netip.Addr{}) + return ui.TableString(addr.String()) + } + return ui.TableString("") + + case 4: + ms := rt.List[row].Match + if len(ms) > 0 { + d := ms[0] + return ui.TableString(d) + } + return ui.TableString("") + case 5: + domains := rt.List[row].Domains + if len(domains) > 0 { + d := utils.GetOneFromMap(domains, "") + return ui.TableString(d) + } + return ui.TableString("") + + } + + return ui.TableInt(0) +} + +func (rt *routeTableH) SetCellValue(m *ui.TableModel, row, column int, value ui.TableValue) { + if ce := utils.CanLogDebug("gui: SetCellValue"); ce != nil { + ce.Write(zap.Int("row", row), + zap.Int("column", column), + zap.Any("value", value), + ) + } + switch column { + case 1: + s := value.(ui.TableString) + rt.List[row].OutTag = string(s) + case 2: + s := value.(ui.TableString) + rt.List[row].Users = map[string]bool{string(s): true} + case 3: + s := value.(ui.TableString) + + na, e := netip.ParseAddr(string(s)) + if e == nil { + rt.List[row].IPs = map[netip.Addr]bool{na: true} + } else { + if ce := utils.CanLogErr("gui: ip wrong"); ce != nil { + ce.Write(zap.Error(e), + zap.String("value", string(s)), + ) + } + } + case 4: + s := value.(ui.TableString) + rt.List[row].Match = []string{string(s)} + + case 5: + s := value.(ui.TableString) + rt.List[row].Domains = map[string]bool{string(s): true} + + } +} diff --git a/cmd/verysimple/main.go b/cmd/verysimple/main.go index c6a4458..5f15297 100644 --- a/cmd/verysimple/main.go +++ b/cmd/verysimple/main.go @@ -34,7 +34,6 @@ func (i *confFileArrayFlags) Set(value string) error { } var ( - //configFileName string configFiles confFileArrayFlags useNativeUrlFormat bool @@ -66,9 +65,11 @@ const ( func init() { mainM = machine.New() + flag.Var(&configFiles, "c", "config files; mutiple files are possible, but must all be toml files, like -c c1.toml -c c2.toml") + flag.IntVar(&utils.LogLevel, "ll", utils.DefaultLL, "log level,0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal") - flag.IntVar(&utils.LogLevelForFile, "llf", -1, "log level for log file,if negative, it will be the same as ll. 0=debug, 1=info, 2=warning, 3=error, 4=dpanic, 5=panic, 6=fatal") + flag.IntVar(&utils.LogLevelForFile, "llf", -1, "log level for log file,if negative, it will be the same as ll.") //有时发现在某些情况下,dns查询或者tcp链接的建立很慢,甚至超过8秒, 所以开放自定义超时时间,便于在不同环境下测试 flag.IntVar(&dialTimeoutSecond, "dt", int(netLayer.DefaultDialTimeout/time.Second), "dial timeout, in second") @@ -91,8 +92,6 @@ func init() { flag.StringVar(&netLayer.GeositeFolder, "geosite", netLayer.DefaultGeositeFolder, "geosite folder name (set it to the relative or absolute path of your geosite/data folder)") flag.StringVar(&utils.ExtraSearchPath, "path", "", "search path for mmdb, geosite and other required files") - flag.Var(&configFiles, "c", "config files; mutiple files are possible, but must all be toml files, like -c c1.toml -c c2.toml") - } func main() { diff --git a/machine/apiServer.go b/machine/apiServer.go index cae6cfb..1b350c2 100644 --- a/machine/apiServer.go +++ b/machine/apiServer.go @@ -127,7 +127,7 @@ func (m *M) runApiServer() { } ser.addServerHandle(mux, "allstate", func(w http.ResponseWriter, r *http.Request) { - m.PrintAllState(w) + m.PrintAllState(w, false) }) ser.addServerHandle(mux, "hotDelete", func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() diff --git a/machine/conf.go b/machine/conf.go index f74d755..e9ad478 100644 --- a/machine/conf.go +++ b/machine/conf.go @@ -217,7 +217,7 @@ func (m *M) SetupListenAndRoute() { m.LoadListenConf(m.standardConf.Listen, false) if len(m.standardConf.Fallbacks) > 0 { - m.ParseFallbacksAtSymbol(m.standardConf.Fallbacks) + m.parseFallbacksAtSymbol(m.standardConf.Fallbacks) } m.routingEnv = proxy.LoadEnvFromStandardConf(&m.standardConf, myCountryISO_3166) @@ -227,7 +227,7 @@ func (m *M) SetupDial() { if len(m.standardConf.Dial) < 1 && m.DefaultOutClient == nil { utils.Warn("no dial in config settings, will add 'direct'") - m.SetDefaultDirectClient() + m.setDefaultDirectClient() return } diff --git a/machine/machine.go b/machine/machine.go index 31fee9d..b075c84 100644 --- a/machine/machine.go +++ b/machine/machine.go @@ -1,9 +1,5 @@ /* -Package machine 定义一个 可以直接运行的有限状态机;这个机器可以直接被可执行文件或者动态库所使用. - -machine把所有运行代理所需要的代码包装起来,对外像一个黑盒子。 - -关键点是不使用任何静态变量,所有变量都放在machine中。 +Package machine 包装运行代理所需的方法,被设计为可轻易被外部程序或库调用, 而无需理解其内部细节 */ package machine @@ -18,6 +14,7 @@ import ( "github.com/dustin/go-humanize" "github.com/e1732a364fed/v2ray_simple" "github.com/e1732a364fed/v2ray_simple/httpLayer" + "github.com/e1732a364fed/v2ray_simple/netLayer" "github.com/e1732a364fed/v2ray_simple/proxy" "github.com/e1732a364fed/v2ray_simple/utils" "go.uber.org/zap" @@ -118,7 +115,7 @@ func (m *M) Start() { for range m.stateReportTicker.C { sw.Prefix = []byte(time.Now().Format("2006-01-02 15:04:05.999 ")) sw.Write([]byte("Current state:\n")) - m.PrintAllStateForHuman(&sw) + m.PrintAllStateForHuman(&sw, false) } }() } @@ -167,7 +164,7 @@ func (m *M) Stop() { m.Unlock() } -func (m *M) SetDefaultDirectClient() { +func (m *M) setDefaultDirectClient() { m.allClients = append(m.allClients, v2ray_simple.DirectClient) m.DefaultOutClient = v2ray_simple.DirectClient @@ -175,7 +172,7 @@ func (m *M) SetDefaultDirectClient() { } // 将fallback配置中的@转化成实际对应的server的地址 -func (m *M) ParseFallbacksAtSymbol(fs []*httpLayer.FallbackConf) { +func (m *M) parseFallbacksAtSymbol(fs []*httpLayer.FallbackConf) { for _, fbConf := range fs { if fbConf.Dest == nil { continue @@ -200,7 +197,7 @@ func (m *M) HasProxyRunning() bool { return len(m.listenCloserList) > 0 } -func (m *M) printAllState_2(w io.Writer) { +func (m *M) printState_proxy(w io.Writer) { for i, s := range m.allServers { fmt.Fprintln(w, "inServer", i, proxy.GetVSI_url(s, "")) @@ -210,7 +207,15 @@ func (m *M) printAllState_2(w io.Writer) { } } -func (m *M) PrintAllState(w io.Writer) { +func (m *M) printState_routePolicy(w io.Writer) { + if rp := m.routingEnv.RoutePolicy; rp != nil { + for i, v := range rp.List { + fmt.Fprintln(w, "route", i, v) + } + } +} + +func (m *M) PrintAllState(w io.Writer, printRouteEnv bool) { if w == nil { w = os.Stdout } @@ -218,11 +223,15 @@ func (m *M) PrintAllState(w io.Writer) { fmt.Fprintln(w, "allDownloadBytesSinceStart", m.AllDownloadBytesSinceStart) fmt.Fprintln(w, "allUploadBytesSinceStart", m.AllUploadBytesSinceStart) - m.printAllState_2(w) + m.printState_proxy(w) + if printRouteEnv { + m.printState_routePolicy(w) + + } } // mimic PrintAllState -func (m *M) PrintAllStateForHuman(w io.Writer) { +func (m *M) PrintAllStateForHuman(w io.Writer, printRouteEnv bool) { if w == nil { w = os.Stdout } @@ -230,6 +239,18 @@ func (m *M) PrintAllStateForHuman(w io.Writer) { fmt.Fprintln(w, "allDownloadBytesSinceStart", humanize.Bytes(m.AllDownloadBytesSinceStart)) fmt.Fprintln(w, "allUploadBytesSinceStart", humanize.Bytes(m.AllUploadBytesSinceStart)) - m.printAllState_2(w) + m.printState_proxy(w) + if printRouteEnv { + m.printState_routePolicy(w) + + } } + +func (m *M) GetRoutePolicy() *netLayer.RoutePolicy { + return m.routingEnv.RoutePolicy +} + +func (m *M) SetRoutePolicy(rp *netLayer.RoutePolicy) { + m.routingEnv.RoutePolicy = rp +} diff --git a/netLayer/route.go b/netLayer/route.go index 0bd35a5..69efe86 100644 --- a/netLayer/route.go +++ b/netLayer/route.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/yl2chen/cidranger" + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" ) // 用于 HasFullOrSubDomain函数 @@ -50,14 +52,16 @@ type TargetDescription struct { UserIdentityStr string } -// Set 是 “集合” 的意思, 是一组相同类型的数据放到一起。 -// -// 这里的相同点,就是它们同属于 将发往一个方向, 即同属一个路由策略。 -// -// 任意一个参数匹配后,都将发往相同的方向,由该方向OutTag 指定。 -// RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起。 -// -// 这里主要通过 ip,域名 和 inTag 进行分流。域名的匹配又分多种方式。 +/* +RouteSet 只负责把一些属性相同的 “网络层/传输层 特征” 放到一起。 + +Set 是 “集合” 的意思, 是一组相同类型的数据放到一起。 + + 这里的相同点,就是它们同属于 将发往一个方向, 即同属一个路由策略。 + +任意一个网络参数匹配后,都将发往相同的方向,由该方向OutTag 指定。 +若还给出了 InTags, Users 或 传输层, 则这些条件都通过后, 才进行网络层判断. +*/ type RouteSet struct { //网络层 NetRanger cidranger.Ranger //一个范围 @@ -273,6 +277,35 @@ func (rs *RouteSet) IsAddrIn(a Addr) bool { return false } +func (rs *RouteSet) Clone() (newOne *RouteSet) { + newOne = &RouteSet{ + NetRanger: cidranger.NewPCTrieRanger(), + IPs: maps.Clone(rs.IPs), + Match: slices.Clone(rs.Match), + Domains: maps.Clone(rs.Domains), + Full: maps.Clone(rs.Full), + Users: maps.Clone(rs.Users), + Geosites: slices.Clone(rs.Geosites), + InTags: maps.Clone(rs.InTags), + OutTags: slices.Clone(rs.OutTags), + OutTag: rs.OutTag, + Regex: slices.Clone(rs.Regex), + Countries: maps.Clone(rs.Countries), + AllowedTransportLayerProtocols: rs.AllowedTransportLayerProtocols, + } + + entries, _ := newOne.NetRanger.CoveredNetworks(*cidranger.AllIPv4) + for _, v := range entries { + newOne.NetRanger.Insert(v) + } + ip6entries, _ := newOne.NetRanger.CoveredNetworks(*cidranger.AllIPv6) + for _, v := range ip6entries { + newOne.NetRanger.Insert(v) + } + + return +} + // 一个完整的 所有RouteSet的列表,进行路由时,直接遍历即可。 // 所谓的路由实际上就是分流。 type RoutePolicy struct { @@ -291,6 +324,13 @@ func (rp *RoutePolicy) AddRouteSet(rs *RouteSet) { } } +func (rp *RoutePolicy) Clone() (newOne RoutePolicy) { + for _, v := range rp.List { + newOne.List = append(newOne.List, v.Clone()) + } + return +} + // 根据td 以及 RoutePolicy的配置 计算出 一个 对应的 proxy.Client 的 tag。 // 默认情况下,始终具有direct这个tag以及 proxy这个tag,无需用户额外在配置文件中指定。 // 默认如果不匹配任何值的话,就会流向 "proxy" tag,也就是客户设置的 remoteClient的值。 diff --git a/utils/algo.go b/utils/algo.go index 50734dd..6ec1c30 100644 --- a/utils/algo.go +++ b/utils/algo.go @@ -9,6 +9,15 @@ import ( "golang.org/x/exp/slices" ) +func GetOneFromMap[T comparable](m map[T]bool, defaultV T) T { + for k := range m { + return k + } + return defaultV +} + +// generics //////////////////////////////////////////////////////////////// + func ArrayToPtrArray[T any](a []T) (r []*T) { for _, v := range a { r = append(r, &v) @@ -61,8 +70,6 @@ func AllSubSets_improve1[T comparable](set []T) (subsets [][]T) { return subsets } -// generics //////////////////////////////////////////////////////////////// - func CloneSlice[T any](a []T) (r []T) { r = make([]T, len(a)) copy(r, a) @@ -82,7 +89,7 @@ func TrimSlice[T any](a []T, deleteIndex int) []T { } return a[:j] - //实际上 golang.org/x/exp/slices 的 Delete 函数也可以 + //实际上 golang.org/x/exp/slices 的 Delete 函数也可以, 但是它是范围删除, 多了一个参数 } // 根据传入的order来对arr重新排序;order必须长度与arr一致,而且包含所有索引