修订cmd,gui;添加gui路由配置页面

This commit is contained in:
e1732a364fed
2000-01-01 00:00:00 +00:00
parent 4a7a287589
commit 12a31e1b89
10 changed files with 304 additions and 38 deletions

View File

@@ -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() {

View File

@@ -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++ {

View File

@@ -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)
}
}

198
cmd/verysimple/gui_route.go Normal file
View File

@@ -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}
}
}

View File

@@ -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() {

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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的值。

View File

@@ -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一致而且包含所有索引