mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-08 10:46:37 +08:00
init
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Config 配置结构体
|
||||
type Config struct {
|
||||
Addr string `json:"addr"` // WebSocket地址
|
||||
Secret string `json:"secret"` // 密钥
|
||||
}
|
||||
|
||||
// LoadConfig 加载配置文件
|
||||
func LoadConfig(configPath string) (*Config, error) {
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("配置文件不存在: %s", configPath)
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 解析JSON
|
||||
var config Config
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("解析配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证必要的配置项
|
||||
if config.Addr == "" {
|
||||
return nil, fmt.Errorf("服务器地址不能为空")
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"addr": "127.0.0.1:6365",
|
||||
"secret": "840b7b728d4c4a51a337966d6e5e78fb"
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
module github.com/go-gost/gost
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.4
|
||||
|
||||
require (
|
||||
github.com/go-gost/core v0.3.1
|
||||
github.com/go-gost/x v0.5.3
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/judwhite/go-svc v1.2.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/alessio/shellescape v1.4.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/coreos/go-iptables v0.5.0 // indirect
|
||||
github.com/danieljoos/wincred v1.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/cors v1.7.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/go-gost/gosocks4 v0.0.1 // indirect
|
||||
github.com/go-gost/gosocks5 v0.4.2 // indirect
|
||||
github.com/go-gost/plugin v0.2.0 // indirect
|
||||
github.com/go-gost/relay v0.5.0 // indirect
|
||||
github.com/go-gost/tls-dissector v0.1.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jonboulle/clockwork v0.2.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/reedsolomon v1.11.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/miekg/dns v1.1.61 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.6 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/transport/v2 v2.0.2 // indirect
|
||||
github.com/pion/udp/v2 v2.0.1 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.48.2 // indirect
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rs/xid v1.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
|
||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/templexxx/cpu v0.1.0 // indirect
|
||||
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/vishvananda/netlink v1.1.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/vulcand/predicate v1.2.0 // indirect
|
||||
github.com/xtaci/kcp-go/v5 v5.6.5 // indirect
|
||||
github.com/xtaci/smux v1.5.31 // indirect
|
||||
github.com/xtaci/tcpraw v1.2.25 // indirect
|
||||
github.com/yl2chen/cidranger v1.0.2 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zalando/go-keyring v0.2.4 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.28.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/go-gost/x => ./x
|
||||
+432
@@ -0,0 +1,432 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coreos/go-iptables v0.5.0 h1:mw6SAibtHKZcNzAsOxjoHIG0gy5YFHhypWSSNc6EjbQ=
|
||||
github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-gost/core v0.3.1 h1:Fo1WvyUTlxSF36d31aRC2LE9UPBDQ4hTMv8oQWoP2wE=
|
||||
github.com/go-gost/core v0.3.1/go.mod h1:WGI43jOka7FAsSAwi/fSMaqxdR+E339ycb4NBGlFr6A=
|
||||
github.com/go-gost/gosocks4 v0.0.1 h1:+k1sec8HlELuQV7rWftIkmy8UijzUt2I6t+iMPlGB2s=
|
||||
github.com/go-gost/gosocks4 v0.0.1/go.mod h1:3B6L47HbU/qugDg4JnoFPHgJXE43Inz8Bah1QaN9qCc=
|
||||
github.com/go-gost/gosocks5 v0.4.2 h1:IianxHTkACPqCwiOAT3MHoMdSUl+SEPSRu1ikawC1Pc=
|
||||
github.com/go-gost/gosocks5 v0.4.2/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||
github.com/go-gost/plugin v0.2.0 h1:Xxqq3oJi6d2dZ/PqqEpefrUckT6aVz8JUxQ/sb7tmTM=
|
||||
github.com/go-gost/plugin v0.2.0/go.mod h1:oN23l+yGDCIP9G3KnDl/I/0zVGOobZUDCB2Z5yYYXts=
|
||||
github.com/go-gost/relay v0.5.0 h1:JG1tgy/KWiVXS0ukuVXvbM0kbYuJTWxYpJ5JwzsCf/c=
|
||||
github.com/go-gost/relay v0.5.0/go.mod h1:lcX+23LCQ3khIeASBo+tJ/WbwXFO32/N5YN6ucuYTG8=
|
||||
github.com/go-gost/tls-dissector v0.1.1 h1:2zUOTPzCQAUQ54Rpy0UEi3JPMQSYsIFSeFeKrzmkCoU=
|
||||
github.com/go-gost/tls-dissector v0.1.1/go.mod h1:/9QfdewqmHdaE362Hv5nDaSWLx3pCmtD870d6GaquXs=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
|
||||
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ=
|
||||
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
|
||||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/judwhite/go-svc v1.2.1 h1:a7fsJzYUa33sfDJRF2N/WXhA+LonCEEY8BJb1tuS5tA=
|
||||
github.com/judwhite/go-svc v1.2.1/go.mod h1:mo/P2JNX8C07ywpP9YtO2gnBgnUiFTHqtsZekJrUuTk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY=
|
||||
github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
|
||||
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pion/dtls/v2 v2.2.6 h1:yXMxKr0Skd+Ub6A8UqXTRLSywskx93ooMRHsQUtd+Z4=
|
||||
github.com/pion/dtls/v2 v2.2.6/go.mod h1:t8fWJCIquY5rlQZwA2yWxUS1+OCrAdXrhVKXB5oD/wY=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/transport/v2 v2.0.2 h1:St+8o+1PEzPT51O9bv+tH/KYYLMNR5Vwm5Z3Qkjsywg=
|
||||
github.com/pion/transport/v2 v2.0.2/go.mod h1:vrz6bUbFr/cjdwbnxq8OdDDzHf7JJfGsIRkxfpZoTA0=
|
||||
github.com/pion/udp/v2 v2.0.1 h1:xP0z6WNux1zWEjhC7onRA3EwwSliXqu1ElUZAQhUP54=
|
||||
github.com/pion/udp/v2 v2.0.1/go.mod h1:B7uvTMP00lzWdyMr/1PVZXtV3wpPIxBRd4Wl6AksXn8=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
|
||||
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg=
|
||||
github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
|
||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ=
|
||||
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
|
||||
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
|
||||
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
|
||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
|
||||
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.5 h1:oxGZNobj3OddrLzwdJYnR/waNgwrL98u02u0DWNHE3k=
|
||||
github.com/xtaci/kcp-go/v5 v5.6.5/go.mod h1:Qy3Zf2tWTdFdEs0E8JvhrX+39r5UDZoYac8anvud7/Q=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM=
|
||||
github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE=
|
||||
github.com/xtaci/smux v1.5.31 h1:3ha7sHtH46h85Iv7MfQogxasuRt1KPRhoFB3S4rmHgU=
|
||||
github.com/xtaci/smux v1.5.31/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
github.com/xtaci/tcpraw v1.2.25 h1:VDlqo0op17JeXBM6e2G9ocCNLOJcw9mZbobMbJjo0vk=
|
||||
github.com/xtaci/tcpraw v1.2.25/go.mod h1:dKyZ2V75s0cZ7cbgJYdxPvms7af0joIeOyx1GgJQbLk=
|
||||
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zalando/go-keyring v0.2.4 h1:wi2xxTqdiwMKbM6TWwi+uJCG/Tum2UV0jqaQhCa9/68=
|
||||
github.com/zalando/go-keyring v0.2.4/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
|
||||
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"services": [
|
||||
{
|
||||
"name": "web_api",
|
||||
"addr": ":18080",
|
||||
"handler": {
|
||||
"type": "api",
|
||||
"auth": {
|
||||
"username": "840b7b728d4c4a51a337966d6e5e78fb",
|
||||
"password": "840b7b728d4c4a51a337966d6e5e78fb"
|
||||
},
|
||||
"metadata": {
|
||||
"pathprefix": "/api"
|
||||
}
|
||||
},
|
||||
"listener": {
|
||||
"type": "tcp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/go-gost/gost/traffic"
|
||||
"log"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
xlogger "github.com/go-gost/x/logger"
|
||||
"github.com/judwhite/go-svc"
|
||||
)
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (l *stringList) String() string {
|
||||
return fmt.Sprintf("%s", *l)
|
||||
}
|
||||
func (l *stringList) Set(value string) error {
|
||||
*l = append(*l, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
outputFormat string
|
||||
services stringList
|
||||
nodes stringList
|
||||
debug bool
|
||||
trace bool
|
||||
apiAddr string
|
||||
metricsAddr string
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds)
|
||||
|
||||
args := strings.Join(os.Args[1:], " ")
|
||||
|
||||
if strings.Contains(args, " -- ") {
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
ret int
|
||||
)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for wid, wargs := range strings.Split(" "+args+" ", " -- ") {
|
||||
wg.Add(1)
|
||||
go func(wid int, wargs string) {
|
||||
defer wg.Done()
|
||||
defer cancel()
|
||||
worker(wid, strings.Split(wargs, " "), &ctx, &ret)
|
||||
}(wid, strings.TrimSpace(wargs))
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
os.Exit(ret)
|
||||
}
|
||||
}
|
||||
|
||||
func worker(id int, args []string, ctx *context.Context, ret *int) {
|
||||
cmd := exec.CommandContext(*ctx, os.Args[0], args...)
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("_GOST_ID=%d", id))
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if cmd.ProcessState.Exited() {
|
||||
*ret = cmd.ProcessState.ExitCode()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var printVersion bool
|
||||
|
||||
flag.Var(&services, "L", "service list")
|
||||
flag.Var(&nodes, "F", "chain node list")
|
||||
flag.StringVar(&cfgFile, "C", "", "configuration file")
|
||||
flag.BoolVar(&printVersion, "V", false, "print version")
|
||||
flag.StringVar(&outputFormat, "O", "", "output format, one of yaml|json format")
|
||||
flag.BoolVar(&debug, "D", false, "debug mode")
|
||||
flag.BoolVar(&trace, "DD", false, "trace mode")
|
||||
flag.StringVar(&apiAddr, "api", "", "api service address")
|
||||
flag.StringVar(&metricsAddr, "metrics", "", "metrics service address")
|
||||
flag.Parse()
|
||||
|
||||
if printVersion {
|
||||
fmt.Fprintf(os.Stdout, "gost %s (%s %s/%s)\n",
|
||||
version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 加载配置文件
|
||||
config, err := LoadConfig("config.json")
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 配置加载失败: %v\n", err)
|
||||
fmt.Println("请确保当前目录存在 config.json 文件")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("✅ 配置加载成功 - addr: %s", config.Addr)
|
||||
|
||||
log := xlogger.NewLogger()
|
||||
logger.SetDefault(log)
|
||||
|
||||
// 创建流量管理器
|
||||
trafficMgr := traffic.NewTrafficManager()
|
||||
defer trafficMgr.Close()
|
||||
|
||||
// 设置全局流量管理器(供 handler 使用)
|
||||
traffic.SetGlobalTrafficManager(trafficMgr)
|
||||
|
||||
// 设置流量记录器给 handler 使用
|
||||
traffic.SetupTrafficRecorder(trafficMgr)
|
||||
|
||||
fmt.Println("✅ 流量管理器已初始化(使用内存存储)")
|
||||
logger.Default().Info("Traffic manager initialized (using memory storage)")
|
||||
|
||||
traffic.SetHTTPReportURL(config.Addr, config.Secret)
|
||||
traffic.StartTrafficReporter(trafficMgr)
|
||||
wsReporter := traffic.StartWebSocketReporterWithConfig(config.Addr, config.Secret)
|
||||
defer wsReporter.Stop()
|
||||
|
||||
p := &program{}
|
||||
|
||||
if err := svc.Run(p); err != nil {
|
||||
logger.Default().Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// GOOS=linux GOARCH=amd64 go build
|
||||
// GOOS=windows GOARCH=amd64 go build -o your_app.exe
|
||||
@@ -0,0 +1,275 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/service"
|
||||
api_service "github.com/go-gost/x/api/service"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/loader"
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
"github.com/go-gost/x/config/parsing/parser"
|
||||
xmetrics "github.com/go-gost/x/metrics"
|
||||
metrics "github.com/go-gost/x/metrics/service"
|
||||
"github.com/go-gost/x/registry"
|
||||
"github.com/judwhite/go-svc"
|
||||
)
|
||||
|
||||
type program struct {
|
||||
srvApi service.Service
|
||||
srvMetrics service.Service
|
||||
srvProfiling *http.Server
|
||||
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (p *program) Init(env svc.Environment) error {
|
||||
parser.Init(parser.Args{
|
||||
CfgFile: cfgFile,
|
||||
Services: services,
|
||||
Nodes: nodes,
|
||||
Debug: debug,
|
||||
Trace: trace,
|
||||
ApiAddr: apiAddr,
|
||||
MetricsAddr: metricsAddr,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) Start() error {
|
||||
cfg, err := parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outputFormat != "" {
|
||||
if err := cfg.Write(os.Stdout, outputFormat); err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
config.Set(cfg)
|
||||
|
||||
if err := loader.Load(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.run(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
p.cancel = cancel
|
||||
go p.reload(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) run(cfg *config.Config) error {
|
||||
for _, svc := range registry.ServiceRegistry().GetAll() {
|
||||
svc := svc
|
||||
go func() {
|
||||
svc.Serve()
|
||||
}()
|
||||
}
|
||||
|
||||
if p.srvApi != nil {
|
||||
p.srvApi.Close()
|
||||
p.srvApi = nil
|
||||
}
|
||||
if cfg.API != nil {
|
||||
s, err := buildApiService(cfg.API)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.srvApi = s
|
||||
|
||||
go func() {
|
||||
defer s.Close()
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@api"})
|
||||
|
||||
log.Info("listening on ", s.Addr())
|
||||
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
xmetrics.Enable(false)
|
||||
if p.srvMetrics != nil {
|
||||
p.srvMetrics.Close()
|
||||
p.srvMetrics = nil
|
||||
}
|
||||
if cfg.Metrics != nil && cfg.Metrics.Addr != "" {
|
||||
s, err := buildMetricsService(cfg.Metrics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.srvMetrics = s
|
||||
|
||||
xmetrics.Enable(true)
|
||||
|
||||
go func() {
|
||||
defer s.Close()
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@metrics"})
|
||||
|
||||
log.Info("listening on ", s.Addr())
|
||||
if err := s.Serve(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if p.srvProfiling != nil {
|
||||
p.srvProfiling.Close()
|
||||
p.srvProfiling = nil
|
||||
}
|
||||
if cfg.Profiling != nil {
|
||||
addr := cfg.Profiling.Addr
|
||||
if addr == "" {
|
||||
addr = ":6060"
|
||||
}
|
||||
s := &http.Server{
|
||||
Addr: addr,
|
||||
}
|
||||
p.srvProfiling = s
|
||||
|
||||
go func() {
|
||||
defer s.Close()
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{"kind": "service", "service": "@profiling"})
|
||||
|
||||
log.Info("listening on ", addr)
|
||||
if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) Stop() error {
|
||||
if p.cancel != nil {
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
for name, srv := range registry.ServiceRegistry().GetAll() {
|
||||
srv.Close()
|
||||
logger.Default().Debugf("service %s shutdown", name)
|
||||
}
|
||||
|
||||
if p.srvApi != nil {
|
||||
p.srvApi.Close()
|
||||
logger.Default().Debug("service @api shutdown")
|
||||
}
|
||||
if p.srvMetrics != nil {
|
||||
p.srvMetrics.Close()
|
||||
logger.Default().Debug("service @metrics shutdown")
|
||||
}
|
||||
if p.srvProfiling != nil {
|
||||
p.srvProfiling.Close()
|
||||
logger.Default().Debug("service @profiling shutdown")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *program) reload(ctx context.Context) {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c:
|
||||
if err := p.reloadConfig(); err != nil {
|
||||
logger.Default().Error(err)
|
||||
} else {
|
||||
logger.Default().Info("config reloaded")
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *program) reloadConfig() error {
|
||||
cfg, err := parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Set(cfg)
|
||||
|
||||
if err := loader.Load(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.run(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildApiService(cfg *config.APIConfig) (service.Service, error) {
|
||||
var authers []auth.Authenticator
|
||||
if auther := auth_parser.ParseAutherFromAuth(cfg.Auth); auther != nil {
|
||||
authers = append(authers, auther)
|
||||
}
|
||||
if cfg.Auther != "" {
|
||||
authers = append(authers, registry.AutherRegistry().Get(cfg.Auther))
|
||||
}
|
||||
|
||||
var auther auth.Authenticator
|
||||
if len(authers) > 0 {
|
||||
auther = xauth.AuthenticatorGroup(authers...)
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
addr := cfg.Addr
|
||||
if strings.HasPrefix(addr, "unix://") {
|
||||
network = "unix"
|
||||
addr = strings.TrimPrefix(addr, "unix://")
|
||||
}
|
||||
return api_service.NewService(
|
||||
network, addr,
|
||||
api_service.PathPrefixOption(cfg.PathPrefix),
|
||||
api_service.AccessLogOption(cfg.AccessLog),
|
||||
api_service.AutherOption(auther),
|
||||
)
|
||||
}
|
||||
|
||||
func buildMetricsService(cfg *config.MetricsConfig) (service.Service, error) {
|
||||
auther := auth_parser.ParseAutherFromAuth(cfg.Auth)
|
||||
if cfg.Auther != "" {
|
||||
auther = registry.AutherRegistry().Get(cfg.Auther)
|
||||
}
|
||||
|
||||
network := "tcp"
|
||||
addr := cfg.Addr
|
||||
if strings.HasPrefix(addr, "unix://") {
|
||||
network = "unix"
|
||||
addr = strings.TrimPrefix(addr, "unix://")
|
||||
}
|
||||
return metrics.NewService(
|
||||
network, addr,
|
||||
metrics.PathOption(cfg.Path),
|
||||
metrics.AutherOption(auther),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
// Register connectors
|
||||
_ "github.com/go-gost/x/connector/direct"
|
||||
_ "github.com/go-gost/x/connector/forward"
|
||||
_ "github.com/go-gost/x/connector/http"
|
||||
_ "github.com/go-gost/x/connector/http2"
|
||||
_ "github.com/go-gost/x/connector/relay"
|
||||
_ "github.com/go-gost/x/connector/router"
|
||||
_ "github.com/go-gost/x/connector/serial"
|
||||
_ "github.com/go-gost/x/connector/sni"
|
||||
_ "github.com/go-gost/x/connector/socks/v4"
|
||||
_ "github.com/go-gost/x/connector/socks/v5"
|
||||
_ "github.com/go-gost/x/connector/ss"
|
||||
_ "github.com/go-gost/x/connector/ss/udp"
|
||||
_ "github.com/go-gost/x/connector/sshd"
|
||||
_ "github.com/go-gost/x/connector/tcp"
|
||||
_ "github.com/go-gost/x/connector/tunnel"
|
||||
_ "github.com/go-gost/x/connector/unix"
|
||||
|
||||
// Register dialers
|
||||
_ "github.com/go-gost/x/dialer/direct"
|
||||
_ "github.com/go-gost/x/dialer/dtls"
|
||||
_ "github.com/go-gost/x/dialer/ftcp"
|
||||
_ "github.com/go-gost/x/dialer/grpc"
|
||||
_ "github.com/go-gost/x/dialer/http2"
|
||||
_ "github.com/go-gost/x/dialer/http2/h2"
|
||||
_ "github.com/go-gost/x/dialer/http3"
|
||||
_ "github.com/go-gost/x/dialer/http3/wt"
|
||||
_ "github.com/go-gost/x/dialer/icmp"
|
||||
_ "github.com/go-gost/x/dialer/kcp"
|
||||
_ "github.com/go-gost/x/dialer/mtcp"
|
||||
_ "github.com/go-gost/x/dialer/mtls"
|
||||
_ "github.com/go-gost/x/dialer/mws"
|
||||
_ "github.com/go-gost/x/dialer/obfs/http"
|
||||
_ "github.com/go-gost/x/dialer/obfs/tls"
|
||||
_ "github.com/go-gost/x/dialer/pht"
|
||||
_ "github.com/go-gost/x/dialer/quic"
|
||||
_ "github.com/go-gost/x/dialer/serial"
|
||||
_ "github.com/go-gost/x/dialer/ssh"
|
||||
_ "github.com/go-gost/x/dialer/sshd"
|
||||
_ "github.com/go-gost/x/dialer/tcp"
|
||||
_ "github.com/go-gost/x/dialer/tls"
|
||||
_ "github.com/go-gost/x/dialer/udp"
|
||||
_ "github.com/go-gost/x/dialer/unix"
|
||||
_ "github.com/go-gost/x/dialer/ws"
|
||||
|
||||
// Register handlers
|
||||
_ "github.com/go-gost/x/handler/api"
|
||||
_ "github.com/go-gost/x/handler/auto"
|
||||
_ "github.com/go-gost/x/handler/dns"
|
||||
_ "github.com/go-gost/x/handler/file"
|
||||
_ "github.com/go-gost/x/handler/forward/local"
|
||||
_ "github.com/go-gost/x/handler/forward/remote"
|
||||
_ "github.com/go-gost/x/handler/http"
|
||||
_ "github.com/go-gost/x/handler/http2"
|
||||
_ "github.com/go-gost/x/handler/http3"
|
||||
_ "github.com/go-gost/x/handler/metrics"
|
||||
_ "github.com/go-gost/x/handler/redirect/tcp"
|
||||
_ "github.com/go-gost/x/handler/redirect/udp"
|
||||
_ "github.com/go-gost/x/handler/relay"
|
||||
_ "github.com/go-gost/x/handler/router"
|
||||
_ "github.com/go-gost/x/handler/serial"
|
||||
_ "github.com/go-gost/x/handler/sni"
|
||||
_ "github.com/go-gost/x/handler/socks/v4"
|
||||
_ "github.com/go-gost/x/handler/socks/v5"
|
||||
_ "github.com/go-gost/x/handler/ss"
|
||||
_ "github.com/go-gost/x/handler/ss/udp"
|
||||
_ "github.com/go-gost/x/handler/sshd"
|
||||
_ "github.com/go-gost/x/handler/tap"
|
||||
_ "github.com/go-gost/x/handler/tun"
|
||||
_ "github.com/go-gost/x/handler/tunnel"
|
||||
_ "github.com/go-gost/x/handler/unix"
|
||||
|
||||
// Register listeners
|
||||
_ "github.com/go-gost/x/listener/dns"
|
||||
_ "github.com/go-gost/x/listener/dtls"
|
||||
_ "github.com/go-gost/x/listener/ftcp"
|
||||
_ "github.com/go-gost/x/listener/grpc"
|
||||
_ "github.com/go-gost/x/listener/http2"
|
||||
_ "github.com/go-gost/x/listener/http2/h2"
|
||||
_ "github.com/go-gost/x/listener/http3"
|
||||
_ "github.com/go-gost/x/listener/http3/h3"
|
||||
_ "github.com/go-gost/x/listener/http3/wt"
|
||||
_ "github.com/go-gost/x/listener/icmp"
|
||||
_ "github.com/go-gost/x/listener/kcp"
|
||||
_ "github.com/go-gost/x/listener/mtcp"
|
||||
_ "github.com/go-gost/x/listener/mtls"
|
||||
_ "github.com/go-gost/x/listener/mws"
|
||||
_ "github.com/go-gost/x/listener/obfs/http"
|
||||
_ "github.com/go-gost/x/listener/obfs/tls"
|
||||
_ "github.com/go-gost/x/listener/pht"
|
||||
_ "github.com/go-gost/x/listener/quic"
|
||||
_ "github.com/go-gost/x/listener/redirect/tcp"
|
||||
_ "github.com/go-gost/x/listener/redirect/udp"
|
||||
_ "github.com/go-gost/x/listener/rtcp"
|
||||
_ "github.com/go-gost/x/listener/rudp"
|
||||
_ "github.com/go-gost/x/listener/serial"
|
||||
_ "github.com/go-gost/x/listener/ssh"
|
||||
_ "github.com/go-gost/x/listener/sshd"
|
||||
_ "github.com/go-gost/x/listener/tap"
|
||||
_ "github.com/go-gost/x/listener/tcp"
|
||||
_ "github.com/go-gost/x/listener/tls"
|
||||
_ "github.com/go-gost/x/listener/tun"
|
||||
_ "github.com/go-gost/x/listener/udp"
|
||||
_ "github.com/go-gost/x/listener/unix"
|
||||
_ "github.com/go-gost/x/listener/ws"
|
||||
)
|
||||
@@ -0,0 +1,176 @@
|
||||
package traffic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 全局变量存储HTTP地址
|
||||
var httpReportURL string
|
||||
|
||||
// TrafficReportItem 流量报告项(压缩格式)
|
||||
type TrafficReportItem struct {
|
||||
N string `json:"n"` // 服务名(name缩写)
|
||||
T string `json:"t"` // 连接类型:conn, cc(type缩写)
|
||||
U int64 `json:"u"` // 上行流量(up缩写)
|
||||
D int64 `json:"d"` // 下行流量(down缩写)
|
||||
}
|
||||
|
||||
// SetHTTPReportURL 设置HTTP报告地址
|
||||
func SetHTTPReportURL(addr string, secret string) {
|
||||
httpReportURL = "http://" + addr + "/flow/upload?secret=" + secret
|
||||
}
|
||||
|
||||
// StartTrafficReporter 启动流量报告任务
|
||||
func StartTrafficReporter(trafficMgr *TrafficManager) {
|
||||
// 检查是否设置了HTTP地址
|
||||
if httpReportURL == "" {
|
||||
fmt.Println("❌ HTTP报告地址未设置,无法启动流量报告任务")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
|
||||
// 原子性地获取并重置流量统计,避免上报期间产生的数据被误清零
|
||||
stats, err := trafficMgr.GetAndResetAllServicesStats(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("获取流量统计失败: %v\n", err)
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
if len(stats) == 0 {
|
||||
fmt.Println("无流量数据需要报告")
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
// 构建报告数据为数组格式,分别处理不同类型的流量
|
||||
var reportItems []TrafficReportItem
|
||||
totalServices := 0
|
||||
totalTraffic := int64(0)
|
||||
|
||||
// 解析服务和类型,分组处理
|
||||
serviceGroups := make(map[string]map[string]map[string]int64) // service -> type -> direction -> bytes
|
||||
|
||||
for serviceKey, serviceStats := range stats {
|
||||
upload := serviceStats["upload"]
|
||||
download := serviceStats["download"]
|
||||
|
||||
// 解析服务名和类型 (format: service:type)
|
||||
var serviceName, serviceType string
|
||||
parts := strings.Split(serviceKey, ":")
|
||||
if len(parts) >= 2 {
|
||||
serviceName = parts[0]
|
||||
serviceType = parts[1]
|
||||
} else {
|
||||
serviceName = serviceKey
|
||||
serviceType = "unknown"
|
||||
}
|
||||
|
||||
if serviceGroups[serviceName] == nil {
|
||||
serviceGroups[serviceName] = make(map[string]map[string]int64)
|
||||
}
|
||||
if serviceGroups[serviceName][serviceType] == nil {
|
||||
serviceGroups[serviceName][serviceType] = make(map[string]int64)
|
||||
}
|
||||
|
||||
serviceGroups[serviceName][serviceType]["upload"] = upload
|
||||
serviceGroups[serviceName][serviceType]["download"] = download
|
||||
|
||||
totalTraffic += upload + download
|
||||
}
|
||||
|
||||
// 为每个服务的每种类型创建报告项
|
||||
for serviceName, types := range serviceGroups {
|
||||
for serviceType, trafficData := range types {
|
||||
// 过滤掉total类型,只保留conn和cc
|
||||
if serviceType == "total" {
|
||||
continue
|
||||
}
|
||||
|
||||
reportItems = append(reportItems, TrafficReportItem{
|
||||
N: serviceName,
|
||||
T: serviceType,
|
||||
U: trafficData["upload"],
|
||||
D: trafficData["download"],
|
||||
})
|
||||
totalServices++
|
||||
}
|
||||
}
|
||||
|
||||
// 发送到HTTP接口
|
||||
success, err := sendTrafficReport(ctx, reportItems)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 发送流量报告失败: %v (数据已清零,本次数据丢失)\n", err)
|
||||
} else if success {
|
||||
fmt.Printf("✅ 流量报告已发送: %d个记录, 总流量: %d bytes\n",
|
||||
totalServices, totalTraffic)
|
||||
} else {
|
||||
fmt.Printf("⚠️ 服务器未确认(非ok响应): %d个记录, 总流量: %d bytes (数据已清零,本次数据丢失)\n",
|
||||
totalServices, totalTraffic)
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf("🚀 流量报告任务已启动 (5秒间隔),目标地址: %s\n", httpReportURL)
|
||||
}
|
||||
|
||||
// sendTrafficReport 发送流量报告到HTTP接口
|
||||
func sendTrafficReport(ctx context.Context, reportItems []TrafficReportItem) (bool, error) {
|
||||
jsonData, err := json.Marshal(reportItems)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("序列化报告数据失败: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", httpReportURL, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("创建HTTP请求失败: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "GOST-Traffic-Reporter/1.0")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("发送HTTP请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("HTTP响应错误: %d %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// 读取响应内容
|
||||
var responseBytes bytes.Buffer
|
||||
_, err = responseBytes.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("读取响应内容失败: %v", err)
|
||||
}
|
||||
|
||||
responseText := strings.TrimSpace(responseBytes.String())
|
||||
|
||||
// 检查响应是否为"ok"
|
||||
if responseText == "ok" {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("服务器响应: %s (期望: ok)", responseText)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
package traffic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/host"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
psnet "github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
// SystemInfo 系统信息结构体
|
||||
type SystemInfo struct {
|
||||
Uptime uint64 `json:"uptime"` // 开机时间 (秒)
|
||||
BytesReceived uint64 `json:"bytes_received"` // 接收字节数
|
||||
BytesTransmitted uint64 `json:"bytes_transmitted"` // 发送字节数
|
||||
CPUUsage float64 `json:"cpu_usage"` // CPU使用率(百分比)
|
||||
MemoryUsage float64 `json:"memory_usage"` // 内存使用率(百分比)
|
||||
}
|
||||
|
||||
// NetworkStats 网络统计信息
|
||||
type NetworkStats struct {
|
||||
BytesReceived uint64 `json:"bytes_received"` // 接收字节数
|
||||
BytesTransmitted uint64 `json:"bytes_transmitted"` // 发送字节数
|
||||
}
|
||||
|
||||
// CPUInfo CPU信息
|
||||
type CPUInfo struct {
|
||||
Usage float64 `json:"usage"` // CPU使用率(百分比)
|
||||
}
|
||||
|
||||
// MemoryInfo 内存信息
|
||||
type MemoryInfo struct {
|
||||
Usage float64 `json:"usage"` // 内存使用率(百分比)
|
||||
}
|
||||
|
||||
type WebSocketReporter struct {
|
||||
url string
|
||||
conn *websocket.Conn
|
||||
reconnectTime time.Duration
|
||||
pingInterval time.Duration
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
connected bool
|
||||
}
|
||||
|
||||
// NewWebSocketReporter 创建一个新的WebSocket报告器
|
||||
func NewWebSocketReporter(serverURL string) *WebSocketReporter {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &WebSocketReporter{
|
||||
url: serverURL,
|
||||
reconnectTime: 5 * time.Second, // 重连间隔
|
||||
pingInterval: 2 * time.Second, // 发送间隔改为2秒
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
connected: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动WebSocket报告器
|
||||
func (w *WebSocketReporter) Start() {
|
||||
go w.run()
|
||||
}
|
||||
|
||||
// Stop 停止WebSocket报告器
|
||||
func (w *WebSocketReporter) Stop() {
|
||||
w.cancel()
|
||||
if w.conn != nil {
|
||||
w.conn.Close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// run 主运行循环
|
||||
func (w *WebSocketReporter) run() {
|
||||
for {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
default:
|
||||
if err := w.connect(); err != nil {
|
||||
fmt.Printf("❌ WebSocket连接失败: %v,%v后重试\n", err, w.reconnectTime)
|
||||
select {
|
||||
case <-time.After(w.reconnectTime):
|
||||
continue
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 连接成功,开始发送消息
|
||||
w.handleConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connect 建立WebSocket连接
|
||||
func (w *WebSocketReporter) connect() error {
|
||||
u, err := url.Parse(w.url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析URL失败: %v", err)
|
||||
}
|
||||
|
||||
dialer := websocket.DefaultDialer
|
||||
dialer.HandshakeTimeout = 10 * time.Second
|
||||
|
||||
conn, _, err := dialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("连接WebSocket失败: %v", err)
|
||||
}
|
||||
|
||||
w.conn = conn
|
||||
w.connected = true
|
||||
|
||||
// 设置关闭处理器来检测连接状态
|
||||
w.conn.SetCloseHandler(func(code int, text string) error {
|
||||
w.connected = false
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConnection 处理WebSocket连接
|
||||
func (w *WebSocketReporter) handleConnection() {
|
||||
defer func() {
|
||||
if w.conn != nil {
|
||||
w.conn.Close()
|
||||
w.conn = nil
|
||||
}
|
||||
w.connected = false
|
||||
}()
|
||||
|
||||
// 主发送循环
|
||||
ticker := time.NewTicker(w.pingInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 检查连接状态
|
||||
if !w.connected {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取系统信息并发送
|
||||
sysInfo := w.collectSystemInfo()
|
||||
if err := w.sendSystemInfo(sysInfo); err != nil {
|
||||
fmt.Printf("❌ 发送系统信息失败: %v,准备重连\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectSystemInfo 收集系统信息
|
||||
func (w *WebSocketReporter) collectSystemInfo() SystemInfo {
|
||||
networkStats := getNetworkStats()
|
||||
cpuInfo := getCPUInfo()
|
||||
memoryInfo := getMemoryInfo()
|
||||
|
||||
return SystemInfo{
|
||||
Uptime: getUptime(),
|
||||
BytesReceived: networkStats.BytesReceived,
|
||||
BytesTransmitted: networkStats.BytesTransmitted,
|
||||
CPUUsage: cpuInfo.Usage,
|
||||
MemoryUsage: memoryInfo.Usage,
|
||||
}
|
||||
}
|
||||
|
||||
// sendSystemInfo 发送系统信息
|
||||
func (w *WebSocketReporter) sendSystemInfo(sysInfo SystemInfo) error {
|
||||
if w.conn == nil || !w.connected {
|
||||
return fmt.Errorf("连接未建立")
|
||||
}
|
||||
|
||||
// 转换为JSON
|
||||
jsonData, err := json.Marshal(sysInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化系统信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 设置写入超时
|
||||
w.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
if err := w.conn.WriteMessage(websocket.TextMessage, jsonData); err != nil {
|
||||
w.connected = false // 标记连接已断开
|
||||
return fmt.Errorf("写入消息失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getHostIP 获取主机IP地址(IPv4优先策略:公网v4 → 公网v6 → 内网v4 → 内网v6)
|
||||
func getHostIP() string {
|
||||
// 先尝试获取IPv4地址
|
||||
ipv4 := getLocalIPv4()
|
||||
if ipv4 != "unknown" && !isPrivateIP(ipv4) {
|
||||
return ipv4
|
||||
}
|
||||
|
||||
// 尝试获取IPv6地址
|
||||
ipv6 := getLocalIPv6()
|
||||
if ipv6 != "unknown" && !isPrivateIP(ipv6) {
|
||||
return ipv6
|
||||
}
|
||||
|
||||
// IPv4公网IP查询服务
|
||||
ipv4Services := []string{
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://api.ipify.org",
|
||||
"https://checkip.amazonaws.com",
|
||||
"https://myip.biturl.top",
|
||||
}
|
||||
|
||||
// IPv6公网IP查询服务
|
||||
ipv6Services := []string{
|
||||
"https://ipv6.icanhazip.com",
|
||||
"https://v6.ident.me",
|
||||
"https://ipv6.myip.biturl.top",
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// 优先尝试获取IPv4公网地址
|
||||
for _, service := range ipv4Services {
|
||||
if ip := getIPFromService(client, service); ip != "" && net.ParseIP(ip).To4() != nil {
|
||||
return strings.TrimSpace(ip)
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4公网IP获取失败,尝试获取IPv6公网地址
|
||||
if ipv6 != "unknown" {
|
||||
for _, service := range ipv6Services {
|
||||
if ip := getIPFromService(client, service); ip != "" && net.ParseIP(ip).To4() == nil {
|
||||
return strings.TrimSpace(ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有公网IP服务都失败,按优先级返回本地IP:IPv4 → IPv6
|
||||
if ipv4 != "unknown" {
|
||||
return ipv4
|
||||
}
|
||||
if ipv6 != "unknown" {
|
||||
return ipv6
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// getIPFromService 从指定服务获取IP地址
|
||||
func getIPFromService(client *http.Client, serviceURL string) string {
|
||||
resp, err := client.Get(serviceURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ""
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip := strings.TrimSpace(string(body))
|
||||
// 简单验证是否为有效的IP地址
|
||||
if net.ParseIP(ip) != nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getLocalIP 获取本地接口IP地址(作为备用方案,保持向后兼容)
|
||||
func getLocalIP() string {
|
||||
return getLocalIPv4()
|
||||
}
|
||||
|
||||
// getLocalIPv4 获取本地IPv4接口地址
|
||||
func getLocalIPv4() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
// getLocalIPv6 获取本地IPv6接口地址
|
||||
func getLocalIPv6() string {
|
||||
// 使用Google的IPv6 DNS服务器
|
||||
conn, err := net.Dial("udp", "[2001:4860:4860::8888]:80")
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
// isPrivateIP 判断IP地址是否为内网地址
|
||||
func isPrivateIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查IPv4私有地址范围
|
||||
if ip.To4() != nil {
|
||||
// 10.0.0.0/8
|
||||
if ip[12] == 10 {
|
||||
return true
|
||||
}
|
||||
// 172.16.0.0/12
|
||||
if ip[12] == 172 && ip[13] >= 16 && ip[13] <= 31 {
|
||||
return true
|
||||
}
|
||||
// 192.168.0.0/16
|
||||
if ip[12] == 192 && ip[13] == 168 {
|
||||
return true
|
||||
}
|
||||
// 127.0.0.0/8 (回环地址)
|
||||
if ip[12] == 127 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 检查IPv6私有地址
|
||||
if ip.To4() == nil {
|
||||
// ::1 (回环地址)
|
||||
if ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
// fc00::/7 (唯一本地地址 Unique Local Addresses)
|
||||
if len(ip) >= 2 && (ip[0]&0xfe) == 0xfc {
|
||||
return true
|
||||
}
|
||||
// fe80::/10 (链路本地地址 Link Local)
|
||||
if len(ip) >= 2 && ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 {
|
||||
return true
|
||||
}
|
||||
// ::ffff:0:0/96 (IPv4映射地址)
|
||||
if len(ip) >= 12 && ip[10] == 0xff && ip[11] == 0xff {
|
||||
// 检查映射的IPv4地址是否为私有地址
|
||||
ipv4 := net.IPv4(ip[12], ip[13], ip[14], ip[15])
|
||||
return isPrivateIP(ipv4.String())
|
||||
}
|
||||
// ::/128 (未指定地址)
|
||||
if ip.IsUnspecified() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getUptime 获取系统开机时间(秒)
|
||||
func getUptime() uint64 {
|
||||
uptime, err := host.Uptime()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return uptime
|
||||
}
|
||||
|
||||
// getNetworkStats 获取网络统计信息
|
||||
func getNetworkStats() NetworkStats {
|
||||
var stats NetworkStats
|
||||
|
||||
ioCounters, err := psnet.IOCounters(true)
|
||||
if err != nil {
|
||||
fmt.Printf("获取网络统计失败: %v\n", err)
|
||||
return stats
|
||||
}
|
||||
|
||||
// 汇总所有非回环接口的流量
|
||||
for _, io := range ioCounters {
|
||||
// 跳过回环接口
|
||||
if io.Name == "lo" || strings.HasPrefix(io.Name, "lo") {
|
||||
continue
|
||||
}
|
||||
|
||||
stats.BytesReceived += io.BytesRecv
|
||||
stats.BytesTransmitted += io.BytesSent
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// getCPUInfo 获取CPU信息
|
||||
func getCPUInfo() CPUInfo {
|
||||
var cpuInfo CPUInfo
|
||||
|
||||
// 获取CPU使用率
|
||||
percentages, err := cpu.Percent(time.Second, false)
|
||||
if err == nil && len(percentages) > 0 {
|
||||
cpuInfo.Usage = percentages[0]
|
||||
}
|
||||
|
||||
return cpuInfo
|
||||
}
|
||||
|
||||
// getMemoryInfo 获取内存信息
|
||||
func getMemoryInfo() MemoryInfo {
|
||||
var memInfo MemoryInfo
|
||||
|
||||
vmStat, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return memInfo
|
||||
}
|
||||
|
||||
memInfo.Usage = vmStat.UsedPercent
|
||||
|
||||
return memInfo
|
||||
}
|
||||
|
||||
// StartWebSocketReporterWithConfig 使用配置启动WebSocket报告器
|
||||
func StartWebSocketReporterWithConfig(Addr string, Secret string) *WebSocketReporter {
|
||||
// 获取本机IP地址
|
||||
localIP := getHostIP()
|
||||
|
||||
// 构建包含本机IP的WebSocket URL
|
||||
var fullURL = "ws://" + Addr + "/system-info?type=1&secret=" + Secret + "&client_ip=" + localIP
|
||||
|
||||
fmt.Printf("🔗 WebSocket连接URL: %s\n", fullURL)
|
||||
|
||||
reporter := NewWebSocketReporter(fullURL)
|
||||
reporter.Start()
|
||||
return reporter
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
var (
|
||||
version = "3.1.0"
|
||||
)
|
||||
@@ -0,0 +1,292 @@
|
||||
package admission
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/matcher"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
whitelist bool
|
||||
matchers []string
|
||||
fileLoader loader.Loader
|
||||
redisLoader loader.Loader
|
||||
httpLoader loader.Loader
|
||||
period time.Duration
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type Option func(opts *options)
|
||||
|
||||
func WhitelistOption(whitelist bool) Option {
|
||||
return func(opts *options) {
|
||||
opts.whitelist = whitelist
|
||||
}
|
||||
}
|
||||
|
||||
func MatchersOption(matchers []string) Option {
|
||||
return func(opts *options) {
|
||||
opts.matchers = matchers
|
||||
}
|
||||
}
|
||||
|
||||
func ReloadPeriodOption(period time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.period = period
|
||||
}
|
||||
}
|
||||
|
||||
func FileLoaderOption(fileLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.fileLoader = fileLoader
|
||||
}
|
||||
}
|
||||
|
||||
func RedisLoaderOption(redisLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.redisLoader = redisLoader
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPLoaderOption(httpLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.httpLoader = httpLoader
|
||||
}
|
||||
}
|
||||
|
||||
func LoggerOption(logger logger.Logger) Option {
|
||||
return func(opts *options) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
type localAdmission struct {
|
||||
ipMatcher matcher.Matcher
|
||||
cidrMatcher matcher.Matcher
|
||||
mu sync.RWMutex
|
||||
cancelFunc context.CancelFunc
|
||||
options options
|
||||
}
|
||||
|
||||
// NewAdmission creates and initializes a new Admission using matcher patterns as its match rules.
|
||||
// The rules will be reversed if the reverse is true.
|
||||
func NewAdmission(opts ...Option) admission.Admission {
|
||||
var options options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
p := &localAdmission{
|
||||
cancelFunc: cancel,
|
||||
options: options,
|
||||
}
|
||||
|
||||
if err := p.reload(ctx); err != nil {
|
||||
options.logger.Warnf("reload: %v", err)
|
||||
}
|
||||
if p.options.period > 0 {
|
||||
go p.periodReload(ctx)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *localAdmission) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
|
||||
if addr == "" || p == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// try to strip the port
|
||||
if host, _, _ := net.SplitHostPort(addr); host != "" {
|
||||
addr = host
|
||||
}
|
||||
|
||||
matched := p.matched(addr)
|
||||
|
||||
b := !p.options.whitelist && !matched ||
|
||||
p.options.whitelist && matched
|
||||
|
||||
if !b {
|
||||
p.options.logger.Debugf("%s is denied", addr)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *localAdmission) periodReload(ctx context.Context) error {
|
||||
period := p.options.period
|
||||
if period < time.Second {
|
||||
period = time.Second
|
||||
}
|
||||
ticker := time.NewTicker(period)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := p.reload(ctx); err != nil {
|
||||
p.options.logger.Warnf("reload: %v", err)
|
||||
// return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *localAdmission) reload(ctx context.Context) error {
|
||||
v, err := p.load(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patterns := append(p.options.matchers, v...)
|
||||
|
||||
var ips []net.IP
|
||||
var inets []*net.IPNet
|
||||
for _, pattern := range patterns {
|
||||
if ip := net.ParseIP(pattern); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
continue
|
||||
}
|
||||
if _, inet, err := net.ParseCIDR(pattern); err == nil {
|
||||
inets = append(inets, inet)
|
||||
continue
|
||||
}
|
||||
if ipAddr, _ := net.ResolveIPAddr("ip", pattern); ipAddr != nil {
|
||||
p.options.logger.Debugf("resolve IP: %s -> %s", pattern, ipAddr)
|
||||
ips = append(ips, ipAddr.IP)
|
||||
}
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.ipMatcher = matcher.IPMatcher(ips)
|
||||
p.cidrMatcher = matcher.CIDRMatcher(inets)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *localAdmission) load(ctx context.Context) (patterns []string, err error) {
|
||||
if p.options.fileLoader != nil {
|
||||
if lister, ok := p.options.fileLoader.(loader.Lister); ok {
|
||||
list, er := lister.List(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
for _, s := range list {
|
||||
if line := p.parseLine(s); line != "" {
|
||||
patterns = append(patterns, line)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r, er := p.options.fileLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
if v, _ := p.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.options.redisLoader != nil {
|
||||
if lister, ok := p.options.redisLoader.(loader.Lister); ok {
|
||||
list, er := lister.List(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("redis loader: %v", er)
|
||||
}
|
||||
patterns = append(patterns, list...)
|
||||
} else {
|
||||
r, er := p.options.redisLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("redis loader: %v", er)
|
||||
}
|
||||
if v, _ := p.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.options.httpLoader != nil {
|
||||
r, er := p.options.httpLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("http loader: %v", er)
|
||||
}
|
||||
if v, _ := p.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
|
||||
p.options.logger.Debugf("load items %d", len(patterns))
|
||||
return
|
||||
}
|
||||
|
||||
func (p *localAdmission) parsePatterns(r io.Reader) (patterns []string, err error) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
if line := p.parseLine(scanner.Text()); line != "" {
|
||||
patterns = append(patterns, line)
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *localAdmission) parseLine(s string) string {
|
||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||
s = s[:n]
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func (p *localAdmission) matched(addr string) bool {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
return p.ipMatcher.Match(addr) ||
|
||||
p.cidrMatcher.Match(addr)
|
||||
}
|
||||
|
||||
func (p *localAdmission) Close() error {
|
||||
p.cancelFunc()
|
||||
if p.options.fileLoader != nil {
|
||||
p.options.fileLoader.Close()
|
||||
}
|
||||
if p.options.redisLoader != nil {
|
||||
p.options.redisLoader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type admissionGroup struct {
|
||||
admissions []admission.Admission
|
||||
}
|
||||
|
||||
func AdmissionGroup(admissions ...admission.Admission) admission.Admission {
|
||||
return &admissionGroup{
|
||||
admissions: admissions,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *admissionGroup) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
|
||||
for _, admission := range p.admissions {
|
||||
if admission != nil && !admission.Admit(ctx, addr, opts...) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/plugin/admission/proto"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type grpcPlugin struct {
|
||||
conn grpc.ClientConnInterface
|
||||
client proto.AdmissionClient
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewGRPCPlugin creates an Admission plugin based on gRPC.
|
||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) admission.Admission {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "admission",
|
||||
"admission": name,
|
||||
})
|
||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
p := &grpcPlugin{
|
||||
conn: conn,
|
||||
log: log,
|
||||
}
|
||||
if conn != nil {
|
||||
p.client = proto.NewAdmissionClient(conn)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) bool {
|
||||
if p.client == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
r, err := p.client.Admit(ctx,
|
||||
&proto.AdmissionRequest{
|
||||
Addr: addr,
|
||||
})
|
||||
if err != nil {
|
||||
p.log.Error(err)
|
||||
return false
|
||||
}
|
||||
return r.Ok
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Close() error {
|
||||
if closer, ok := p.conn.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package admission
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
type httpPluginRequest struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
|
||||
type httpPluginResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
url string
|
||||
client *http.Client
|
||||
header http.Header
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewHTTPPlugin creates an Admission plugin based on HTTP.
|
||||
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) admission.Admission {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &httpPlugin{
|
||||
url: url,
|
||||
client: plugin.NewHTTPClient(&options),
|
||||
header: options.Header,
|
||||
log: logger.Default().WithFields(map[string]any{
|
||||
"kind": "admission",
|
||||
"admission": name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Admit(ctx context.Context, addr string, opts ...admission.Option) (ok bool) {
|
||||
if p.client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb := httpPluginRequest{
|
||||
Addr: addr,
|
||||
}
|
||||
v, err := json.Marshal(&rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.header != nil {
|
||||
req.Header = p.header.Clone()
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
res := httpPluginResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return
|
||||
}
|
||||
return res.OK
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/metadata"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
"github.com/go-gost/x/internal/net/udp"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnsupport = errors.New("unsupported operation")
|
||||
)
|
||||
|
||||
type serverConn struct {
|
||||
net.Conn
|
||||
admission admission.Admission
|
||||
}
|
||||
|
||||
func WrapConn(admission admission.Admission, c net.Conn) net.Conn {
|
||||
if admission == nil {
|
||||
return c
|
||||
}
|
||||
return &serverConn{
|
||||
Conn: c,
|
||||
admission: admission,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverConn) Read(b []byte) (n int, err error) {
|
||||
if c.admission != nil &&
|
||||
!c.admission.Admit(context.Background(), c.RemoteAddr().String()) {
|
||||
err = io.EOF
|
||||
return
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *serverConn) SyscallConn() (rc syscall.RawConn, err error) {
|
||||
if sc, ok := c.Conn.(syscall.Conn); ok {
|
||||
rc, err = sc.SyscallConn()
|
||||
return
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *serverConn) Metadata() metadata.Metadata {
|
||||
if md, ok := c.Conn.(metadata.Metadatable); ok {
|
||||
return md.Metadata()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.PacketConn
|
||||
admission admission.Admission
|
||||
}
|
||||
|
||||
func WrapPacketConn(admission admission.Admission, pc net.PacketConn) net.PacketConn {
|
||||
if admission == nil {
|
||||
return pc
|
||||
}
|
||||
return &packetConn{
|
||||
PacketConn: pc,
|
||||
admission: admission,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *packetConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
for {
|
||||
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.admission != nil &&
|
||||
!c.admission.Admit(context.Background(), addr.String()) {
|
||||
continue
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *packetConn) Metadata() metadata.Metadata {
|
||||
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
|
||||
return md.Metadata()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type udpConn struct {
|
||||
net.PacketConn
|
||||
admission admission.Admission
|
||||
}
|
||||
|
||||
func WrapUDPConn(admission admission.Admission, pc net.PacketConn) udp.Conn {
|
||||
return &udpConn{
|
||||
PacketConn: pc,
|
||||
admission: admission,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpConn) RemoteAddr() net.Addr {
|
||||
if nc, ok := c.PacketConn.(xnet.RemoteAddr); ok {
|
||||
return nc.RemoteAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpConn) SetReadBuffer(n int) error {
|
||||
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
|
||||
return nc.SetReadBuffer(n)
|
||||
}
|
||||
return errUnsupport
|
||||
}
|
||||
|
||||
func (c *udpConn) SetWriteBuffer(n int) error {
|
||||
if nc, ok := c.PacketConn.(xnet.SetBuffer); ok {
|
||||
return nc.SetWriteBuffer(n)
|
||||
}
|
||||
return errUnsupport
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||
if nc, ok := c.PacketConn.(io.Reader); ok {
|
||||
n, err = nc.Read(b)
|
||||
return
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
for {
|
||||
n, addr, err = c.PacketConn.ReadFrom(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.admission != nil &&
|
||||
!c.admission.Admit(context.Background(), addr.String()) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *udpConn) ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error) {
|
||||
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
|
||||
for {
|
||||
n, addr, err = nc.ReadFromUDP(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.admission != nil &&
|
||||
!c.admission.Admit(context.Background(), addr.String()) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error) {
|
||||
if nc, ok := c.PacketConn.(udp.ReadUDP); ok {
|
||||
for {
|
||||
n, oobn, flags, addr, err = nc.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.admission != nil &&
|
||||
!c.admission.Admit(context.Background(), addr.String()) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Write(b []byte) (n int, err error) {
|
||||
if nc, ok := c.PacketConn.(io.Writer); ok {
|
||||
n, err = nc.Write(b)
|
||||
return
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
n, err = c.PacketConn.WriteTo(p, addr)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error) {
|
||||
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
|
||||
n, err = nc.WriteToUDP(b, addr)
|
||||
return
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error) {
|
||||
if nc, ok := c.PacketConn.(udp.WriteUDP); ok {
|
||||
n, oobn, err = nc.WriteMsgUDP(b, oob, addr)
|
||||
return
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) SyscallConn() (rc syscall.RawConn, err error) {
|
||||
if nc, ok := c.PacketConn.(xnet.SyscallConn); ok {
|
||||
return nc.SyscallConn()
|
||||
}
|
||||
err = errUnsupport
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) SetDSCP(n int) error {
|
||||
if nc, ok := c.PacketConn.(xnet.SetDSCP); ok {
|
||||
return nc.SetDSCP(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *udpConn) Metadata() metadata.Metadata {
|
||||
if md, ok := c.PacketConn.(metadata.Metadatable); ok {
|
||||
return md.Metadata()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package wrapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
net.Listener
|
||||
admission admission.Admission
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
func WrapListener(admission admission.Admission, ln net.Listener) net.Listener {
|
||||
if admission == nil {
|
||||
return ln
|
||||
}
|
||||
return &listener{
|
||||
Listener: ln,
|
||||
admission: admission,
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *listener) Accept() (net.Conn, error) {
|
||||
for {
|
||||
c, err := ln.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ln.admission != nil &&
|
||||
!ln.admission.Admit(context.Background(), c.RemoteAddr().String()) {
|
||||
c.Close()
|
||||
continue
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed swagger.yaml
|
||||
swaggerDoc embed.FS
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
AccessLog bool
|
||||
PathPrefix string
|
||||
Auther auth.Authenticator
|
||||
}
|
||||
|
||||
func Register(r *gin.Engine, opts *Options) {
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
|
||||
r.Use(
|
||||
cors.New((cors.Config{
|
||||
AllowAllOrigins: true,
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"*"},
|
||||
AllowPrivateNetwork: true,
|
||||
})),
|
||||
gin.Recovery(),
|
||||
)
|
||||
if opts.AccessLog {
|
||||
r.Use(mwLogger())
|
||||
}
|
||||
|
||||
router := r.Group("")
|
||||
if opts.PathPrefix != "" {
|
||||
router = router.Group(opts.PathPrefix)
|
||||
}
|
||||
|
||||
router.StaticFS("/docs", http.FS(swaggerDoc))
|
||||
|
||||
config := router.Group("/config")
|
||||
config.Use(mwBasicAuth(opts.Auther))
|
||||
|
||||
config.GET("", getConfig)
|
||||
config.POST("", saveConfig)
|
||||
|
||||
config.POST("/reload", reloadConfig)
|
||||
|
||||
config.POST("/services", createService)
|
||||
config.PUT("/services/:service", updateService)
|
||||
config.DELETE("/services/:service", deleteService)
|
||||
config.POST("/services/:service/pause", pauseService)
|
||||
config.POST("/services/:service/resume", resumeService)
|
||||
config.GET("/services/:service/status", getServiceStatus)
|
||||
config.POST("/services/batch", createServices)
|
||||
config.PUT("/services/batch", updateServices)
|
||||
config.DELETE("/services/batch", deleteServices)
|
||||
config.POST("/services/batch/pause", pauseServices)
|
||||
config.POST("/services/batch/resume", resumeServices)
|
||||
|
||||
config.POST("/chains", createChain)
|
||||
config.PUT("/chains/:chain", updateChain)
|
||||
config.DELETE("/chains/:chain", deleteChain)
|
||||
|
||||
config.POST("/hops", createHop)
|
||||
config.PUT("/hops/:hop", updateHop)
|
||||
config.DELETE("/hops/:hop", deleteHop)
|
||||
|
||||
config.POST("/authers", createAuther)
|
||||
config.PUT("/authers/:auther", updateAuther)
|
||||
config.DELETE("/authers/:auther", deleteAuther)
|
||||
|
||||
config.POST("/admissions", createAdmission)
|
||||
config.PUT("/admissions/:admission", updateAdmission)
|
||||
config.DELETE("/admissions/:admission", deleteAdmission)
|
||||
|
||||
config.POST("/bypasses", createBypass)
|
||||
config.PUT("/bypasses/:bypass", updateBypass)
|
||||
config.DELETE("/bypasses/:bypass", deleteBypass)
|
||||
|
||||
config.POST("/resolvers", createResolver)
|
||||
config.PUT("/resolvers/:resolver", updateResolver)
|
||||
config.DELETE("/resolvers/:resolver", deleteResolver)
|
||||
|
||||
config.POST("/hosts", createHosts)
|
||||
config.PUT("/hosts/:hosts", updateHosts)
|
||||
config.DELETE("/hosts/:hosts", deleteHosts)
|
||||
|
||||
config.POST("/ingresses", createIngress)
|
||||
config.PUT("/ingresses/:ingress", updateIngress)
|
||||
config.DELETE("/ingresses/:ingress", deleteIngress)
|
||||
|
||||
config.POST("/routers", createRouter)
|
||||
config.PUT("/routers/:router", updateRouter)
|
||||
config.DELETE("/routers/:router", deleteRouter)
|
||||
|
||||
config.POST("/observers", createObserver)
|
||||
config.PUT("/observers/:observer", updateObserver)
|
||||
config.DELETE("/observers/:observer", deleteObserver)
|
||||
|
||||
config.POST("/recorders", createRecorder)
|
||||
config.PUT("/recorders/:recorder", updateRecorder)
|
||||
config.DELETE("/recorders/:recorder", deleteRecorder)
|
||||
|
||||
config.POST("/sds", createSD)
|
||||
config.PUT("/sds/:sd", updateSD)
|
||||
config.DELETE("/sds/:sd", deleteSD)
|
||||
|
||||
config.POST("/limiters", createLimiter)
|
||||
config.PUT("/limiters/:limiter", updateLimiter)
|
||||
config.DELETE("/limiters/:limiter", deleteLimiter)
|
||||
|
||||
config.POST("/climiters", createConnLimiter)
|
||||
config.PUT("/climiters/:limiter", updateConnLimiter)
|
||||
config.DELETE("/climiters/:limiter", deleteConnLimiter)
|
||||
|
||||
config.POST("/rlimiters", createRateLimiter)
|
||||
config.PUT("/rlimiters/:limiter", updateRateLimiter)
|
||||
config.DELETE("/rlimiters/:limiter", deleteRateLimiter)
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/registry"
|
||||
"github.com/go-gost/x/service"
|
||||
)
|
||||
|
||||
type serviceStatus interface {
|
||||
Status() *service.Status
|
||||
}
|
||||
|
||||
// swagger:parameters getConfigRequest
|
||||
type getConfigRequest struct {
|
||||
// output format, one of yaml|json, default is json.
|
||||
// in: query
|
||||
Format string `form:"format" json:"format"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response getConfigResponse
|
||||
type getConfigResponse struct {
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
func getConfig(ctx *gin.Context) {
|
||||
// swagger:route GET /config Config getConfigRequest
|
||||
//
|
||||
// Get current config.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: getConfigResponse
|
||||
|
||||
var req getConfigRequest
|
||||
ctx.ShouldBindQuery(&req)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for _, svc := range c.Services {
|
||||
if svc == nil {
|
||||
continue
|
||||
}
|
||||
s := registry.ServiceRegistry().Get(svc.Name)
|
||||
ss, ok := s.(serviceStatus)
|
||||
if ok && ss != nil {
|
||||
status := ss.Status()
|
||||
svc.Status = &config.ServiceStatus{
|
||||
CreateTime: status.CreateTime().Unix(),
|
||||
State: string(status.State()),
|
||||
}
|
||||
if st := status.Stats(); st != nil {
|
||||
svc.Status.Stats = &config.ServiceStats{
|
||||
TotalConns: st.Get(stats.KindTotalConns),
|
||||
CurrentConns: st.Get(stats.KindCurrentConns),
|
||||
TotalErrs: st.Get(stats.KindTotalErrs),
|
||||
InputBytes: st.Get(stats.KindInputBytes),
|
||||
OutputBytes: st.Get(stats.KindOutputBytes),
|
||||
}
|
||||
}
|
||||
for _, ev := range status.Events() {
|
||||
if !ev.Time.IsZero() {
|
||||
svc.Status.Events = append(svc.Status.Events, config.ServiceEvent{
|
||||
Time: ev.Time.Unix(),
|
||||
Msg: ev.Message,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
var resp getConfigResponse
|
||||
resp.Config = config.Global()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
switch req.Format {
|
||||
case "yaml":
|
||||
default:
|
||||
req.Format = "json"
|
||||
}
|
||||
|
||||
resp.Config.Write(buf, req.Format)
|
||||
|
||||
contentType := "application/json"
|
||||
if req.Format == "yaml" {
|
||||
contentType = "text/x-yaml"
|
||||
}
|
||||
|
||||
ctx.Data(http.StatusOK, contentType, buf.Bytes())
|
||||
}
|
||||
|
||||
// swagger:parameters saveConfigRequest
|
||||
type saveConfigRequest struct {
|
||||
// output format, one of yaml|json, default is yaml.
|
||||
// in: query
|
||||
Format string `form:"format" json:"format"`
|
||||
// file path, default is gost.yaml|gost.json in current working directory.
|
||||
// in: query
|
||||
Path string `form:"path" json:"path"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response saveConfigResponse
|
||||
type saveConfigResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func saveConfig(ctx *gin.Context) {
|
||||
// swagger:route POST /config Config saveConfigRequest
|
||||
//
|
||||
// Save current config to file (gost.yaml or gost.json).
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: saveConfigResponse
|
||||
|
||||
var req saveConfigRequest
|
||||
ctx.ShouldBindQuery(&req)
|
||||
|
||||
file := "gost.yaml"
|
||||
switch req.Format {
|
||||
case "json":
|
||||
file = "gost.json"
|
||||
default:
|
||||
req.Format = "yaml"
|
||||
}
|
||||
|
||||
if req.Path != "" {
|
||||
file = req.Path
|
||||
}
|
||||
|
||||
f, err := os.Create(file)
|
||||
if err != nil {
|
||||
writeError(ctx, &Error{
|
||||
statusCode: http.StatusInternalServerError,
|
||||
Code: ErrCodeSaveConfigFailed,
|
||||
Msg: fmt.Sprintf("create file: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := config.Global().Write(f, req.Format); err != nil {
|
||||
writeError(ctx, &Error{
|
||||
statusCode: http.StatusInternalServerError,
|
||||
Code: ErrCodeSaveConfigFailed,
|
||||
Msg: fmt.Sprintf("save config: %s", err.Error()),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/admission"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createAdmissionRequest
|
||||
type createAdmissionRequest struct {
|
||||
// in: body
|
||||
Data config.AdmissionConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createAdmissionResponse
|
||||
type createAdmissionResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createAdmission(ctx *gin.Context) {
|
||||
// swagger:route POST /config/admissions Admission createAdmissionRequest
|
||||
//
|
||||
// Create a new admission, the name of admission must be unique in admission list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createAdmissionResponse
|
||||
|
||||
var req createAdmissionRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "admission name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseAdmission(&req.Data)
|
||||
|
||||
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Admissions = append(c.Admissions, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateAdmissionRequest
|
||||
type updateAdmissionRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Admission string `uri:"admission" json:"admission"`
|
||||
// in: body
|
||||
Data config.AdmissionConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateAdmissionResponse
|
||||
type updateAdmissionResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateAdmission(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/admissions/{admission} Admission updateAdmissionRequest
|
||||
//
|
||||
// Update admission by name, the admission must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateAdmissionResponse
|
||||
|
||||
var req updateAdmissionRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Admission)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseAdmission(&req.Data)
|
||||
|
||||
registry.AdmissionRegistry().Unregister(name)
|
||||
|
||||
if err := registry.AdmissionRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("admission %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Admissions {
|
||||
if c.Admissions[i].Name == name {
|
||||
c.Admissions[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteAdmissionRequest
|
||||
type deleteAdmissionRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Admission string `uri:"admission" json:"admission"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteAdmissionResponse
|
||||
type deleteAdmissionResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteAdmission(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/admissions/{admission} Admission deleteAdmissionRequest
|
||||
//
|
||||
// Delete admission by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteAdmissionResponse
|
||||
|
||||
var req deleteAdmissionRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Admission)
|
||||
|
||||
if !registry.AdmissionRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("admission %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.AdmissionRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
admissiones := c.Admissions
|
||||
c.Admissions = nil
|
||||
for _, s := range admissiones {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Admissions = append(c.Admissions, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/auth"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createAutherRequest
|
||||
type createAutherRequest struct {
|
||||
// in: body
|
||||
Data config.AutherConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createAutherResponse
|
||||
type createAutherResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createAuther(ctx *gin.Context) {
|
||||
// swagger:route POST /config/authers Auther createAutherRequest
|
||||
//
|
||||
// Create a new auther, the name of the auther must be unique in auther list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createAutherResponse
|
||||
|
||||
var req createAutherRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "auther name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseAuther(&req.Data)
|
||||
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Authers = append(c.Authers, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateAutherRequest
|
||||
type updateAutherRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Auther string `uri:"auther" json:"auther"`
|
||||
// in: body
|
||||
Data config.AutherConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateAutherResponse
|
||||
type updateAutherResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateAuther(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/authers/{auther} Auther updateAutherRequest
|
||||
//
|
||||
// Update auther by name, the auther must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateAutherResponse
|
||||
|
||||
var req updateAutherRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Auther)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseAuther(&req.Data)
|
||||
registry.AutherRegistry().Unregister(name)
|
||||
|
||||
if err := registry.AutherRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("auther %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Authers {
|
||||
if c.Authers[i].Name == name {
|
||||
c.Authers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteAutherRequest
|
||||
type deleteAutherRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Auther string `uri:"auther" json:"auther"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteAutherResponse
|
||||
type deleteAutherResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteAuther(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/authers/{auther} Auther deleteAutherRequest
|
||||
//
|
||||
// Delete auther by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteAutherResponse
|
||||
|
||||
var req deleteAutherRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Auther)
|
||||
|
||||
if !registry.AutherRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("auther %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.AutherRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
authers := c.Authers
|
||||
c.Authers = nil
|
||||
for _, s := range authers {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Authers = append(c.Authers, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createBypassRequest
|
||||
type createBypassRequest struct {
|
||||
// in: body
|
||||
Data config.BypassConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createBypassResponse
|
||||
type createBypassResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createBypass(ctx *gin.Context) {
|
||||
// swagger:route POST /config/bypasses Bypass createBypassRequest
|
||||
//
|
||||
// Create a new bypass, the name of bypass must be unique in bypass list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createBypassResponse
|
||||
|
||||
var req createBypassRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "bypass name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseBypass(&req.Data)
|
||||
|
||||
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Bypasses = append(c.Bypasses, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateBypassRequest
|
||||
type updateBypassRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Bypass string `uri:"bypass" json:"bypass"`
|
||||
// in: body
|
||||
Data config.BypassConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateBypassResponse
|
||||
type updateBypassResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateBypass(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/bypasses/{bypass} Bypass updateBypassRequest
|
||||
//
|
||||
// Update bypass by name, the bypass must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateBypassResponse
|
||||
|
||||
var req updateBypassRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Bypass)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseBypass(&req.Data)
|
||||
|
||||
registry.BypassRegistry().Unregister(name)
|
||||
|
||||
if err := registry.BypassRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("bypass %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Bypasses {
|
||||
if c.Bypasses[i].Name == name {
|
||||
c.Bypasses[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteBypassRequest
|
||||
type deleteBypassRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Bypass string `uri:"bypass" json:"bypass"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteBypassResponse
|
||||
type deleteBypassResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteBypass(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/bypasses/{bypass} Bypass deleteBypassRequest
|
||||
//
|
||||
// Delete bypass by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteBypassResponse
|
||||
|
||||
var req deleteBypassRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Bypass)
|
||||
|
||||
if !registry.BypassRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("bypass %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.BypassRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
bypasses := c.Bypasses
|
||||
c.Bypasses = nil
|
||||
for _, s := range bypasses {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Bypasses = append(c.Bypasses, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/chain"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createChainRequest
|
||||
type createChainRequest struct {
|
||||
// in: body
|
||||
Data config.ChainConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createChainResponse
|
||||
type createChainResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createChain(ctx *gin.Context) {
|
||||
// swagger:route POST /config/chains Chain createChainRequest
|
||||
//
|
||||
// Create a new chain, the name of chain must be unique in chain list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createChainResponse
|
||||
|
||||
var req createChainRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "chain name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Chains = append(c.Chains, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateChainRequest
|
||||
type updateChainRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
// chain name
|
||||
Chain string `uri:"chain" json:"chain"`
|
||||
// in: body
|
||||
Data config.ChainConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateChainResponse
|
||||
type updateChainResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateChain(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/chains/{chain} Chain updateChainRequest
|
||||
//
|
||||
// Update chain by name, the chain must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateChainResponse
|
||||
|
||||
var req updateChainRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Chain)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseChain(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create chain %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ChainRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ChainRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("chain %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Chains {
|
||||
if c.Chains[i].Name == name {
|
||||
c.Chains[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteChainRequest
|
||||
type deleteChainRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Chain string `uri:"chain" json:"chain"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteChainResponse
|
||||
type deleteChainResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteChain(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/chains/{chain} Chain deleteChainRequest
|
||||
//
|
||||
// Delete chain by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteChainResponse
|
||||
|
||||
var req deleteChainRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Chain)
|
||||
|
||||
if !registry.ChainRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("chain %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ChainRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
chains := c.Chains
|
||||
c.Chains = nil
|
||||
for _, s := range chains {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Chains = append(c.Chains, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/limiter"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createConnLimiterRequest
|
||||
type createConnLimiterRequest struct {
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createConnLimiterResponse
|
||||
type createConnLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createConnLimiter(ctx *gin.Context) {
|
||||
// swagger:route POST /config/climiters Limiter createConnLimiterRequest
|
||||
//
|
||||
// Create a new conn limiter, the name of limiter must be unique in limiter list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createConnLimiterResponse
|
||||
|
||||
var req createConnLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseConnLimiter(&req.Data)
|
||||
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create limmiter %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.CLimiters = append(c.CLimiters, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateConnLimiterRequest
|
||||
type updateConnLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateConnLimiterResponse
|
||||
type updateConnLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateConnLimiter(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/climiters/{limiter} Limiter updateConnLimiterRequest
|
||||
//
|
||||
// Update conn limiter by name, the limiter must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateConnLimiterResponse
|
||||
|
||||
var req updateConnLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseConnLimiter(&req.Data)
|
||||
|
||||
registry.ConnLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ConnLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.CLimiters {
|
||||
if c.CLimiters[i].Name == name {
|
||||
c.CLimiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteConnLimiterRequest
|
||||
type deleteConnLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteConnLimiterResponse
|
||||
type deleteConnLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteConnLimiter(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/climiters/{limiter} Limiter deleteConnLimiterRequest
|
||||
//
|
||||
// Delete conn limiter by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteConnLimiterResponse
|
||||
|
||||
var req deleteConnLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.ConnLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ConnLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.CLimiters
|
||||
c.CLimiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.CLimiters = append(c.CLimiters, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/hop"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createHopRequest
|
||||
type createHopRequest struct {
|
||||
// in: body
|
||||
Data config.HopConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createHopResponse
|
||||
type createHopResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createHop(ctx *gin.Context) {
|
||||
// swagger:route POST /config/hops Hop createHopRequest
|
||||
//
|
||||
// Create a new hop, the name of hop must be unique in hop list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createHopResponse
|
||||
|
||||
var req createHopRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hop name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Hops = append(c.Hops, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateHopRequest
|
||||
type updateHopRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
// hop name
|
||||
Hop string `uri:"hop" json:"hop"`
|
||||
// in: body
|
||||
Data config.HopConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateHopResponse
|
||||
type updateHopResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateHop(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/hops/{hop} Hop updateHopRequest
|
||||
//
|
||||
// Update hop by name, the hop must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateHopResponse
|
||||
|
||||
var req updateHopRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Hop)
|
||||
if !registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseHop(&req.Data, logger.Default())
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create hop %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.HopRegistry().Unregister(name)
|
||||
|
||||
if err := registry.HopRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hop %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Hops {
|
||||
if c.Hops[i].Name == name {
|
||||
c.Hops[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteHopRequest
|
||||
type deleteHopRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Hop string `uri:"hop" json:"hop"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteHopResponse
|
||||
type deleteHopResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteHop(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/hops/{hop} Hop deleteHopRequest
|
||||
//
|
||||
// Delete hop by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteHopResponse
|
||||
|
||||
var req deleteHopRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Hop)
|
||||
|
||||
if !registry.HopRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hop %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.HopRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
hops := c.Hops
|
||||
c.Hops = nil
|
||||
for _, s := range hops {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Hops = append(c.Hops, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/hosts"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createHostsRequest
|
||||
type createHostsRequest struct {
|
||||
// in: body
|
||||
Data config.HostsConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createHostsResponse
|
||||
type createHostsesponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createHosts(ctx *gin.Context) {
|
||||
// swagger:route POST /config/hosts Hosts createHostsRequest
|
||||
//
|
||||
// Create a new hosts, the name of the hosts must be unique in hosts list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createHostsResponse
|
||||
|
||||
var req createHostsRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "hosts name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseHostMapper(&req.Data)
|
||||
|
||||
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Hosts = append(c.Hosts, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateHostsRequest
|
||||
type updateHostsRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Hosts string `uri:"hosts" json:"hosts"`
|
||||
// in: body
|
||||
Data config.HostsConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateHostsResponse
|
||||
type updateHostsResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateHosts(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/hosts/{hosts} Hosts updateHostsRequest
|
||||
//
|
||||
// Update hosts by name, the hosts must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateHostsResponse
|
||||
|
||||
var req updateHostsRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Hosts)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseHostMapper(&req.Data)
|
||||
|
||||
registry.HostsRegistry().Unregister(name)
|
||||
|
||||
if err := registry.HostsRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("hosts %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Hosts {
|
||||
if c.Hosts[i].Name == name {
|
||||
c.Hosts[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteHostsRequest
|
||||
type deleteHostsRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Hosts string `uri:"hosts" json:"hosts"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteHostsResponse
|
||||
type deleteHostsResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteHosts(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/hosts/{hosts} Hosts deleteHostsRequest
|
||||
//
|
||||
// Delete hosts by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteHostsResponse
|
||||
|
||||
var req deleteHostsRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Hosts)
|
||||
|
||||
if !registry.HostsRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("hosts %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.HostsRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
hosts := c.Hosts
|
||||
c.Hosts = nil
|
||||
for _, s := range hosts {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Hosts = append(c.Hosts, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/ingress"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createIngressRequest
|
||||
type createIngressRequest struct {
|
||||
// in: body
|
||||
Data config.IngressConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createIngressResponse
|
||||
type createIngressResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createIngress(ctx *gin.Context) {
|
||||
// swagger:route POST /config/ingresses Ingress createIngressRequest
|
||||
//
|
||||
// Create a new ingress, the name of the ingress must be unique in ingress list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createIngressResponse
|
||||
|
||||
var req createIngressRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "ingress name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseIngress(&req.Data)
|
||||
|
||||
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Ingresses = append(c.Ingresses, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateIngressRequest
|
||||
type updateIngressRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Ingress string `uri:"ingress" json:"ingress"`
|
||||
// in: body
|
||||
Data config.IngressConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateIngressResponse
|
||||
type updateIngressResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateIngress(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/ingresses/{ingress} Ingress updateIngressRequest
|
||||
//
|
||||
// Update ingress by name, the ingress must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateIngressResponse
|
||||
|
||||
var req updateIngressRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Ingress)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseIngress(&req.Data)
|
||||
|
||||
registry.IngressRegistry().Unregister(name)
|
||||
|
||||
if err := registry.IngressRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("ingress %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Ingresses {
|
||||
if c.Ingresses[i].Name == name {
|
||||
c.Ingresses[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteIngressRequest
|
||||
type deleteIngressRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Ingress string `uri:"ingress" json:"ingress"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteIngressResponse
|
||||
type deleteIngressResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteIngress(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/ingresses/{ingress} Ingress deleteIngressRequest
|
||||
//
|
||||
// Delete ingress by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteIngressResponse
|
||||
|
||||
var req deleteIngressRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Ingress)
|
||||
|
||||
if !registry.IngressRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("ingress %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.IngressRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
ingresses := c.Ingresses
|
||||
c.Ingresses = nil
|
||||
for _, s := range ingresses {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Ingresses = append(c.Ingresses, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/limiter"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createLimiterRequest
|
||||
type createLimiterRequest struct {
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createLimiterResponse
|
||||
type createLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createLimiter(ctx *gin.Context) {
|
||||
// swagger:route POST /config/limiters Limiter createLimiterRequest
|
||||
//
|
||||
// Create a new limiter, the name of limiter must be unique in limiter list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createLimiterResponse
|
||||
|
||||
var req createLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseTrafficLimiter(&req.Data)
|
||||
|
||||
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Limiters = append(c.Limiters, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateLimiterRequest
|
||||
type updateLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateLimiterResponse
|
||||
type updateLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateLimiter(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/limiters/{limiter} Limiter updateLimiterRequest
|
||||
//
|
||||
// Update limiter by name, the limiter must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateLimiterResponse
|
||||
|
||||
var req updateLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseTrafficLimiter(&req.Data)
|
||||
|
||||
registry.TrafficLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.TrafficLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Limiters {
|
||||
if c.Limiters[i].Name == name {
|
||||
c.Limiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteLimiterRequest
|
||||
type deleteLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteLimiterResponse
|
||||
type deleteLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteLimiter(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/limiters/{limiter} Limiter deleteLimiterRequest
|
||||
//
|
||||
// Delete limiter by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteLimiterResponse
|
||||
|
||||
var req deleteLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.TrafficLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.TrafficLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.Limiters
|
||||
c.Limiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Limiters = append(c.Limiters, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/observer"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createObserverRequest
|
||||
type createObserverRequest struct {
|
||||
// in: body
|
||||
Data config.ObserverConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createObserverResponse
|
||||
type createObserverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createObserver(ctx *gin.Context) {
|
||||
// swagger:route POST /config/observers Observer createObserverRequest
|
||||
//
|
||||
// Create a new observer, the name of the observer must be unique in observer list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createObserverResponse
|
||||
|
||||
var req createObserverRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "observer name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ObserverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseObserver(&req.Data)
|
||||
|
||||
if err := registry.ObserverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Observers = append(c.Observers, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateObserverRequest
|
||||
type updateObserverRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Observer string `uri:"observer" json:"observer"`
|
||||
// in: body
|
||||
Data config.ObserverConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateObserverResponse
|
||||
type updateObserverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateObserver(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/observers/{observer} Observer updateObserverRequest
|
||||
//
|
||||
// Update observer by name, the observer must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateObserverResponse
|
||||
|
||||
var req updateObserverRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Observer)
|
||||
|
||||
if !registry.ObserverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("observer %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseObserver(&req.Data)
|
||||
|
||||
registry.ObserverRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ObserverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("observer %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Observers {
|
||||
if c.Observers[i].Name == name {
|
||||
c.Observers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteObserverRequest
|
||||
type deleteObserverRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Observer string `uri:"observer" json:"observer"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteObserverResponse
|
||||
type deleteObserverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteObserver(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/observers/{observer} Observer deleteObserverRequest
|
||||
//
|
||||
// Delete observer by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteObserverResponse
|
||||
|
||||
var req deleteObserverRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Observer)
|
||||
|
||||
if !registry.ObserverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("observer %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ObserverRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
observers := c.Observers
|
||||
c.Observers = nil
|
||||
for _, s := range observers {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Observers = append(c.Observers, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/limiter"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createRateLimiterRequest
|
||||
type createRateLimiterRequest struct {
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createRateLimiterResponse
|
||||
type createRateLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createRateLimiter(ctx *gin.Context) {
|
||||
// swagger:route POST /config/rlimiters Limiter createRateLimiterRequest
|
||||
//
|
||||
// Create a new rate limiter, the name of limiter must be unique in limiter list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createRateLimiterResponse
|
||||
|
||||
var req createRateLimiterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "limiter name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseRateLimiter(&req.Data)
|
||||
|
||||
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.RLimiters = append(c.RLimiters, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateRateLimiterRequest
|
||||
type updateRateLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
// in: body
|
||||
Data config.LimiterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateRateLimiterResponse
|
||||
type updateRateLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateRateLimiter(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/rlimiters/{limiter} Limiter updateRateLimiterRequest
|
||||
//
|
||||
// Update rate limiter by name, the limiter must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateRateLimiterResponse
|
||||
|
||||
var req updateRateLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseRateLimiter(&req.Data)
|
||||
|
||||
registry.RateLimiterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.RateLimiterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("limiter %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.RLimiters {
|
||||
if c.RLimiters[i].Name == name {
|
||||
c.RLimiters[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteRateLimiterRequest
|
||||
type deleteRateLimiterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Limiter string `uri:"limiter" json:"limiter"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteRateLimiterResponse
|
||||
type deleteRateLimiterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteRateLimiter(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/rlimiters/{limiter} Limiter deleteRateLimiterRequest
|
||||
//
|
||||
// Delete rate limiter by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteRateLimiterResponse
|
||||
|
||||
var req deleteRateLimiterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Limiter)
|
||||
|
||||
if !registry.RateLimiterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("limiter %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.RateLimiterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
limiteres := c.RLimiters
|
||||
c.RLimiters = nil
|
||||
for _, s := range limiteres {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.RLimiters = append(c.RLimiters, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/recorder"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createRecorderRequest
|
||||
type createRecorderRequest struct {
|
||||
// in: body
|
||||
Data config.RecorderConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createRecorderResponse
|
||||
type createRecorderResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createRecorder(ctx *gin.Context) {
|
||||
// swagger:route POST /config/recorders Recorder createRecorderRequest
|
||||
//
|
||||
// Create a new recorder, the name of the recorder must be unique in recorder list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createRecorderResponse
|
||||
|
||||
var req createRecorderRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "recorder name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.RecorderRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name)))
|
||||
return
|
||||
}
|
||||
v := parser.ParseRecorder(&req.Data)
|
||||
|
||||
if err := registry.RecorderRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Recorders = append(c.Recorders, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateRecorderRequest
|
||||
type updateRecorderRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Recorder string `uri:"recorder" json:"recorder"`
|
||||
// in: body
|
||||
Data config.RecorderConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateRecorderResponse
|
||||
type updateRecorderResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateRecorder(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/recorders/{recorder} Recorder updateRecorderRequest
|
||||
//
|
||||
// Update recorder by name, the recorder must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateRecorderResponse
|
||||
|
||||
var req updateRecorderRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Recorder)
|
||||
|
||||
if !registry.RecorderRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("recorder %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseRecorder(&req.Data)
|
||||
|
||||
registry.RecorderRegistry().Unregister(name)
|
||||
|
||||
if err := registry.RecorderRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("recorder %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Recorders {
|
||||
if c.Recorders[i].Name == name {
|
||||
c.Recorders[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteRecorderRequest
|
||||
type deleteRecorderRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Recorder string `uri:"recorder" json:"recorder"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteRecorderResponse
|
||||
type deleteRecorderResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteRecorder(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/recorders/{recorder} Recorder deleteRecorderRequest
|
||||
//
|
||||
// Delete recorder by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteRecorderResponse
|
||||
|
||||
var req deleteRecorderRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Recorder)
|
||||
|
||||
if !registry.RecorderRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("recorder %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.RecorderRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
recorders := c.Recorders
|
||||
c.Recorders = nil
|
||||
for _, s := range recorders {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Recorders = append(c.Recorders, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/loader"
|
||||
"github.com/go-gost/x/config/parsing/parser"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters reloadConfigRequest
|
||||
type reloadConfigRequest struct{}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response reloadConfigResponse
|
||||
type reloadConfigResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func reloadConfig(ctx *gin.Context) {
|
||||
// swagger:route POST /config/reload Reload reloadConfigRequest
|
||||
//
|
||||
// Hot reload config.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: reloadConfigResponse
|
||||
|
||||
cfg, err := parser.Parse()
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
config.Set(cfg)
|
||||
|
||||
if err := loader.Load(cfg); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
for _, svc := range registry.ServiceRegistry().GetAll() {
|
||||
svc := svc
|
||||
go func() {
|
||||
svc.Serve()
|
||||
}()
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/resolver"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createResolverRequest
|
||||
type createResolverRequest struct {
|
||||
// in: body
|
||||
Data config.ResolverConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createResolverResponse
|
||||
type createResolverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createResolver(ctx *gin.Context) {
|
||||
// swagger:route POST /config/resolvers Resolver createResolverRequest
|
||||
//
|
||||
// Create a new resolver, the name of the resolver must be unique in resolver list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createResolverResponse
|
||||
|
||||
var req createResolverRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "resolver name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := parser.ParseResolver(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Resolvers = append(c.Resolvers, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateResolverRequest
|
||||
type updateResolverRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Resolver string `uri:"resolver" json:"resolver"`
|
||||
// in: body
|
||||
Data config.ResolverConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateResolverResponse
|
||||
type updateResolverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateResolver(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/resolvers/{resolver} Resolver updateResolverRequest
|
||||
//
|
||||
// Update resolver by name, the resolver must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateResolverResponse
|
||||
|
||||
var req updateResolverRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Resolver)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v, err := parser.ParseResolver(&req.Data)
|
||||
if err != nil {
|
||||
writeError(ctx, NewError(http.StatusInternalServerError, ErrCodeFailed, fmt.Sprintf("create resolver %s failed: %s", name, err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
registry.ResolverRegistry().Unregister(name)
|
||||
|
||||
if err := registry.ResolverRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("resolver %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Resolvers {
|
||||
if c.Resolvers[i].Name == name {
|
||||
c.Resolvers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteResolverRequest
|
||||
type deleteResolverRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Resolver string `uri:"resolver" json:"resolver"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteResolverResponse
|
||||
type deleteResolverResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteResolver(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/resolvers/{resolver} Resolver deleteResolverRequest
|
||||
//
|
||||
// Delete resolver by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteResolverResponse
|
||||
|
||||
var req deleteResolverRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Resolver)
|
||||
|
||||
if !registry.ResolverRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("resolver %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.ResolverRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
resolvers := c.Resolvers
|
||||
c.Resolvers = nil
|
||||
for _, s := range resolvers {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Resolvers = append(c.Resolvers, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/router"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createRouterRequest
|
||||
type createRouterRequest struct {
|
||||
// in: body
|
||||
Data config.RouterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createRouterResponse
|
||||
type createRouterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createRouter(ctx *gin.Context) {
|
||||
// swagger:route POST /config/routers Router createRouterRequest
|
||||
//
|
||||
// Create a new router, the name of the router must be unique in router list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createRouterResponse
|
||||
|
||||
var req createRouterRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "router name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.RouterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseRouter(&req.Data)
|
||||
|
||||
if err := registry.RouterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.Routers = append(c.Routers, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateRouterRequest
|
||||
type updateRouterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Router string `uri:"router" json:"router"`
|
||||
// in: body
|
||||
Data config.RouterConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateRouterResponse
|
||||
type updateRouterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateRouter(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/routers/{router} Router updateRouterRequest
|
||||
//
|
||||
// Update router by name, the router must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateRouterResponse
|
||||
|
||||
var req updateRouterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Router)
|
||||
|
||||
if !registry.RouterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("router %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseRouter(&req.Data)
|
||||
|
||||
registry.RouterRegistry().Unregister(name)
|
||||
|
||||
if err := registry.RouterRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("router %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.Routers {
|
||||
if c.Routers[i].Name == name {
|
||||
c.Routers[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteRouterRequest
|
||||
type deleteRouterRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
Router string `uri:"router" json:"router"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteRouterResponse
|
||||
type deleteRouterResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteRouter(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/routers/{router} Router deleteRouterRequest
|
||||
//
|
||||
// Delete router by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteRouterResponse
|
||||
|
||||
var req deleteRouterRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.Router)
|
||||
|
||||
if !registry.RouterRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("router %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.RouterRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
routeres := c.Routers
|
||||
c.Routers = nil
|
||||
for _, s := range routeres {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.Routers = append(c.Routers, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/x/config"
|
||||
parser "github.com/go-gost/x/config/parsing/sd"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
// swagger:parameters createSDRequest
|
||||
type createSDRequest struct {
|
||||
// in: body
|
||||
Data config.SDConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response createSDResponse
|
||||
type createSDResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func createSD(ctx *gin.Context) {
|
||||
// swagger:route POST /config/sds SD createSDRequest
|
||||
//
|
||||
// Create a new SD, the name of the SD must be unique in SD list.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: createSDResponse
|
||||
|
||||
var req createSDRequest
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.Data.Name)
|
||||
if name == "" {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeInvalid, "sd name is required"))
|
||||
return
|
||||
}
|
||||
req.Data.Name = name
|
||||
|
||||
if registry.SDRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
v := parser.ParseSD(&req.Data)
|
||||
|
||||
if err := registry.SDRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
c.SDs = append(c.SDs, &req.Data)
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters updateSDRequest
|
||||
type updateSDRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
SD string `uri:"sd" json:"sd"`
|
||||
// in: body
|
||||
Data config.SDConfig `json:"data"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response updateSDResponse
|
||||
type updateSDResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func updateSD(ctx *gin.Context) {
|
||||
// swagger:route PUT /config/sds/{sd} SD updateSDRequest
|
||||
//
|
||||
// Update SD by name, the SD must already exist.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: updateSDResponse
|
||||
|
||||
var req updateSDRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
ctx.ShouldBindJSON(&req.Data)
|
||||
|
||||
name := strings.TrimSpace(req.SD)
|
||||
|
||||
if !registry.SDRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("sd %s not found", name)))
|
||||
return
|
||||
}
|
||||
|
||||
req.Data.Name = name
|
||||
|
||||
v := parser.ParseSD(&req.Data)
|
||||
|
||||
registry.SDRegistry().Unregister(name)
|
||||
|
||||
if err := registry.SDRegistry().Register(name, v); err != nil {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeDup, fmt.Sprintf("sd %s already exists", name)))
|
||||
return
|
||||
}
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
for i := range c.SDs {
|
||||
if c.SDs[i].Name == name {
|
||||
c.SDs[i] = &req.Data
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
|
||||
// swagger:parameters deleteSDRequest
|
||||
type deleteSDRequest struct {
|
||||
// in: path
|
||||
// required: true
|
||||
SD string `uri:"sd" json:"sd"`
|
||||
}
|
||||
|
||||
// successful operation.
|
||||
// swagger:response deleteSDResponse
|
||||
type deleteSDResponse struct {
|
||||
Data Response
|
||||
}
|
||||
|
||||
func deleteSD(ctx *gin.Context) {
|
||||
// swagger:route DELETE /config/sds/{sd} SD deleteSDRequest
|
||||
//
|
||||
// Delete SD by name.
|
||||
//
|
||||
// Security:
|
||||
// basicAuth: []
|
||||
//
|
||||
// Responses:
|
||||
// 200: deleteSDResponse
|
||||
|
||||
var req deleteSDRequest
|
||||
ctx.ShouldBindUri(&req)
|
||||
|
||||
name := strings.TrimSpace(req.SD)
|
||||
|
||||
if !registry.SDRegistry().IsRegistered(name) {
|
||||
writeError(ctx, NewError(http.StatusBadRequest, ErrCodeNotFound, fmt.Sprintf("sd %s not found", name)))
|
||||
return
|
||||
}
|
||||
registry.SDRegistry().Unregister(name)
|
||||
|
||||
config.OnUpdate(func(c *config.Config) error {
|
||||
sds := c.SDs
|
||||
c.SDs = nil
|
||||
for _, s := range sds {
|
||||
if s.Name == name {
|
||||
continue
|
||||
}
|
||||
c.SDs = append(c.SDs, s)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusOK, Response{
|
||||
Msg: "OK",
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
// Documentation of Web API.
|
||||
//
|
||||
// Schemes: https, http
|
||||
// BasePath: /
|
||||
// Version: 1.0.0
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// SecurityDefinitions:
|
||||
// basicAuth:
|
||||
// type: basic
|
||||
//
|
||||
// swagger:meta
|
||||
package api
|
||||
@@ -0,0 +1,63 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalid = &Error{statusCode: http.StatusBadRequest, Code: 40001, Msg: "object invalid"}
|
||||
ErrDup = &Error{statusCode: http.StatusBadRequest, Code: 40002, Msg: "object duplicated"}
|
||||
ErrCreate = &Error{statusCode: http.StatusConflict, Code: 40003, Msg: "object creation failed"}
|
||||
ErrNotFound = &Error{statusCode: http.StatusBadRequest, Code: 40004, Msg: "object not found"}
|
||||
ErrSave = &Error{statusCode: http.StatusInternalServerError, Code: 40005, Msg: "save config failed"}
|
||||
)
|
||||
|
||||
type ErrCode int
|
||||
|
||||
const (
|
||||
ErrCodeInvalid = 40001
|
||||
ErrCodeDup = 40002
|
||||
ErrCodeFailed = 40003
|
||||
ErrCodeNotFound = 40004
|
||||
ErrCodeSaveConfigFailed = 40005
|
||||
)
|
||||
|
||||
// Error is an api error.
|
||||
type Error struct {
|
||||
statusCode int
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func NewError(status, code int, msg string) error {
|
||||
return &Error{
|
||||
statusCode: status,
|
||||
Code: code,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
b, _ := json.Marshal(e)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func writeError(c *gin.Context, err error) {
|
||||
// c.Set(HTTPResponseTag, err)
|
||||
c.JSON(getStatusCode(err), err)
|
||||
}
|
||||
|
||||
func getStatusCode(err error) int {
|
||||
if err == nil {
|
||||
return http.StatusOK
|
||||
}
|
||||
if e, ok := err.(*Error); ok {
|
||||
if e.statusCode >= http.StatusOK && e.statusCode < 600 {
|
||||
return e.statusCode
|
||||
}
|
||||
}
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
)
|
||||
|
||||
func mwLogger() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// start time
|
||||
startTime := time.Now()
|
||||
// Processing request
|
||||
ctx.Next()
|
||||
duration := time.Since(startTime)
|
||||
|
||||
logger.Default().WithFields(map[string]any{
|
||||
"kind": "api",
|
||||
"method": ctx.Request.Method,
|
||||
"uri": ctx.Request.RequestURI,
|
||||
"code": ctx.Writer.Status(),
|
||||
"client": ctx.ClientIP(),
|
||||
"duration": duration,
|
||||
}).Infof("| %3d | %13v | %15s | %-7s %s",
|
||||
ctx.Writer.Status(), duration, ctx.ClientIP(), ctx.Request.Method, ctx.Request.RequestURI)
|
||||
}
|
||||
}
|
||||
|
||||
func mwBasicAuth(auther auth.Authenticator) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if auther == nil {
|
||||
return
|
||||
}
|
||||
u, p, _ := c.Request.BasicAuth()
|
||||
if _, ok := auther.Authenticate(c, u, p); !ok {
|
||||
c.Writer.Header().Set("WWW-Authenticate", "Basic")
|
||||
c.JSON(http.StatusUnauthorized, Response{
|
||||
Code: http.StatusUnauthorized,
|
||||
Msg: "Unauthorized",
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/service"
|
||||
"github.com/go-gost/x/api"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
accessLog bool
|
||||
pathPrefix string
|
||||
auther auth.Authenticator
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
func PathPrefixOption(pathPrefix string) Option {
|
||||
return func(o *options) {
|
||||
o.pathPrefix = pathPrefix
|
||||
}
|
||||
}
|
||||
|
||||
func AccessLogOption(enable bool) Option {
|
||||
return func(o *options) {
|
||||
o.accessLog = enable
|
||||
}
|
||||
}
|
||||
|
||||
func AutherOption(auther auth.Authenticator) Option {
|
||||
return func(o *options) {
|
||||
o.auther = auther
|
||||
}
|
||||
}
|
||||
|
||||
type server struct {
|
||||
s *http.Server
|
||||
ln net.Listener
|
||||
cclose chan struct{}
|
||||
}
|
||||
|
||||
func NewService(network, addr string, opts ...Option) (service.Service, error) {
|
||||
if network == "" {
|
||||
network = "tcp"
|
||||
}
|
||||
ln, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var options options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
|
||||
r := gin.New()
|
||||
api.Register(r, &api.Options{
|
||||
AccessLog: options.accessLog,
|
||||
PathPrefix: options.pathPrefix,
|
||||
Auther: options.auther,
|
||||
})
|
||||
|
||||
return &server{
|
||||
s: &http.Server{
|
||||
Handler: r,
|
||||
},
|
||||
ln: ln,
|
||||
cclose: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) Serve() error {
|
||||
return s.s.Serve(s.ln)
|
||||
}
|
||||
|
||||
func (s *server) Addr() net.Addr {
|
||||
return s.ln.Addr()
|
||||
}
|
||||
|
||||
func (s *server) Close() error {
|
||||
return s.s.Close()
|
||||
}
|
||||
|
||||
func (s *server) IsClosed() bool {
|
||||
select {
|
||||
case <-s.cclose:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,280 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
xlogger "github.com/go-gost/x/logger"
|
||||
)
|
||||
|
||||
type options struct {
|
||||
auths map[string]string
|
||||
fileLoader loader.Loader
|
||||
redisLoader loader.Loader
|
||||
httpLoader loader.Loader
|
||||
period time.Duration
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type Option func(opts *options)
|
||||
|
||||
func AuthsOption(auths map[string]string) Option {
|
||||
return func(opts *options) {
|
||||
opts.auths = auths
|
||||
}
|
||||
}
|
||||
|
||||
func ReloadPeriodOption(period time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.period = period
|
||||
}
|
||||
}
|
||||
|
||||
func FileLoaderOption(fileLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.fileLoader = fileLoader
|
||||
}
|
||||
}
|
||||
|
||||
func RedisLoaderOption(redisLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.redisLoader = redisLoader
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPLoaderOption(httpLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.httpLoader = httpLoader
|
||||
}
|
||||
}
|
||||
|
||||
func LoggerOption(logger logger.Logger) Option {
|
||||
return func(opts *options) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// authenticator is an Authenticator that authenticates client by key-value pairs.
|
||||
type authenticator struct {
|
||||
kvs map[string]string
|
||||
mu sync.RWMutex
|
||||
cancelFunc context.CancelFunc
|
||||
options options
|
||||
}
|
||||
|
||||
// NewAuthenticator creates an Authenticator that authenticates client by pre-defined user mapping.
|
||||
func NewAuthenticator(opts ...Option) auth.Authenticator {
|
||||
var options options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
if options.logger == nil {
|
||||
options.logger = xlogger.Nop()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
p := &authenticator{
|
||||
kvs: make(map[string]string),
|
||||
cancelFunc: cancel,
|
||||
options: options,
|
||||
}
|
||||
|
||||
if err := p.reload(ctx); err != nil {
|
||||
options.logger.Warnf("reload: %v", err)
|
||||
}
|
||||
if p.options.period > 0 {
|
||||
go p.periodReload(ctx)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Authenticate checks the validity of the provided user-password pair.
|
||||
func (p *authenticator) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
|
||||
if p == nil {
|
||||
return "", true
|
||||
}
|
||||
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
if len(p.kvs) == 0 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
v, ok := p.kvs[user]
|
||||
return user, ok && (v == "" || password == v)
|
||||
}
|
||||
|
||||
func (p *authenticator) periodReload(ctx context.Context) error {
|
||||
period := p.options.period
|
||||
if period < time.Second {
|
||||
period = time.Second
|
||||
}
|
||||
ticker := time.NewTicker(period)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := p.reload(ctx); err != nil {
|
||||
p.options.logger.Warnf("reload: %v", err)
|
||||
// return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *authenticator) reload(ctx context.Context) (err error) {
|
||||
kvs := make(map[string]string)
|
||||
for k, v := range p.options.auths {
|
||||
kvs[k] = v
|
||||
}
|
||||
|
||||
m, err := p.load(ctx)
|
||||
for k, v := range m {
|
||||
kvs[k] = v
|
||||
}
|
||||
|
||||
p.options.logger.Debugf("load items %d", len(m))
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
p.kvs = kvs
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *authenticator) load(ctx context.Context) (m map[string]string, err error) {
|
||||
m = make(map[string]string)
|
||||
|
||||
if p.options.fileLoader != nil {
|
||||
if mapper, ok := p.options.fileLoader.(loader.Mapper); ok {
|
||||
auths, er := mapper.Map(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
m = auths
|
||||
} else {
|
||||
r, er := p.options.fileLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
if auths, _ := p.parseAuths(r); auths != nil {
|
||||
m = auths
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.options.redisLoader != nil {
|
||||
if mapper, ok := p.options.fileLoader.(loader.Mapper); ok {
|
||||
auths, er := mapper.Map(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
for k, v := range auths {
|
||||
m[k] = v
|
||||
}
|
||||
} else {
|
||||
r, er := p.options.redisLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("redis loader: %v", er)
|
||||
}
|
||||
if auths, _ := p.parseAuths(r); auths != nil {
|
||||
for k, v := range auths {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if p.options.httpLoader != nil {
|
||||
r, er := p.options.httpLoader.Load(ctx)
|
||||
if er != nil {
|
||||
p.options.logger.Warnf("http loader: %v", er)
|
||||
}
|
||||
if auths, _ := p.parseAuths(r); auths != nil {
|
||||
for k, v := range auths {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *authenticator) parseAuths(r io.Reader) (auths map[string]string, err error) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
auths = make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := strings.Replace(scanner.Text(), "\t", " ", -1)
|
||||
line = strings.TrimSpace(line)
|
||||
if n := strings.IndexByte(line, '#'); n == 0 {
|
||||
continue
|
||||
}
|
||||
sp := strings.SplitN(line, " ", 2)
|
||||
if len(sp) == 1 {
|
||||
if k := strings.TrimSpace(sp[0]); k != "" {
|
||||
auths[k] = ""
|
||||
}
|
||||
}
|
||||
if len(sp) == 2 {
|
||||
if k := strings.TrimSpace(sp[0]); k != "" {
|
||||
auths[k] = strings.TrimSpace(sp[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
|
||||
func (p *authenticator) Close() error {
|
||||
p.cancelFunc()
|
||||
if p.options.fileLoader != nil {
|
||||
p.options.fileLoader.Close()
|
||||
}
|
||||
if p.options.redisLoader != nil {
|
||||
p.options.redisLoader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type authenticatorGroup struct {
|
||||
authers []auth.Authenticator
|
||||
}
|
||||
|
||||
func AuthenticatorGroup(authers ...auth.Authenticator) auth.Authenticator {
|
||||
return &authenticatorGroup{
|
||||
authers: authers,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *authenticatorGroup) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
|
||||
if len(p.authers) == 0 {
|
||||
return "", false
|
||||
}
|
||||
for _, auther := range p.authers {
|
||||
if auther == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if id, ok := auther.Authenticate(ctx, user, password, opts...); ok {
|
||||
return id, ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/plugin/auth/proto"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type grpcPlugin struct {
|
||||
conn grpc.ClientConnInterface
|
||||
client proto.AuthenticatorClient
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewGRPCPlugin creates an Authenticator plugin based on gRPC.
|
||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) auth.Authenticator {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "auther",
|
||||
"auther": name,
|
||||
})
|
||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
p := &grpcPlugin{
|
||||
conn: conn,
|
||||
log: log,
|
||||
}
|
||||
|
||||
if conn != nil {
|
||||
p.client = proto.NewAuthenticatorClient(conn)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Authenticate checks the validity of the provided user-password pair.
|
||||
func (p *grpcPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (string, bool) {
|
||||
if p.client == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
r, err := p.client.Authenticate(ctx,
|
||||
&proto.AuthenticateRequest{
|
||||
Username: user,
|
||||
Password: password,
|
||||
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
|
||||
})
|
||||
if err != nil {
|
||||
p.log.Error(err)
|
||||
return "", false
|
||||
}
|
||||
return r.Id, r.Ok
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Close() error {
|
||||
if closer, ok := p.conn.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
type httpPluginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Client string `json:"client"`
|
||||
}
|
||||
|
||||
type httpPluginResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
url string
|
||||
client *http.Client
|
||||
header http.Header
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewHTTPPlugin creates an Authenticator plugin based on HTTP.
|
||||
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) auth.Authenticator {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &httpPlugin{
|
||||
url: url,
|
||||
client: plugin.NewHTTPClient(&options),
|
||||
header: options.Header,
|
||||
log: logger.Default().WithFields(map[string]any{
|
||||
"kind": "auther",
|
||||
"auther": name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Authenticate(ctx context.Context, user, password string, opts ...auth.Option) (id string, ok bool) {
|
||||
if p.client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
rb := httpPluginRequest{
|
||||
Username: user,
|
||||
Password: password,
|
||||
Client: string(ctxvalue.ClientAddrFromContext(ctx)),
|
||||
}
|
||||
v, err := json.Marshal(&rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.header != nil {
|
||||
req.Header = p.header.Clone()
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
res := httpPluginResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return
|
||||
}
|
||||
return res.ID, res.OK
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package bypass
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/matcher"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBypass = errors.New("bypass")
|
||||
)
|
||||
|
||||
type options struct {
|
||||
whitelist bool
|
||||
matchers []string
|
||||
fileLoader loader.Loader
|
||||
redisLoader loader.Loader
|
||||
httpLoader loader.Loader
|
||||
period time.Duration
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type Option func(opts *options)
|
||||
|
||||
func WhitelistOption(whitelist bool) Option {
|
||||
return func(opts *options) {
|
||||
opts.whitelist = whitelist
|
||||
}
|
||||
}
|
||||
|
||||
func MatchersOption(matchers []string) Option {
|
||||
return func(opts *options) {
|
||||
opts.matchers = matchers
|
||||
}
|
||||
}
|
||||
|
||||
func ReloadPeriodOption(period time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.period = period
|
||||
}
|
||||
}
|
||||
|
||||
func FileLoaderOption(fileLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.fileLoader = fileLoader
|
||||
}
|
||||
}
|
||||
|
||||
func RedisLoaderOption(redisLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.redisLoader = redisLoader
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPLoaderOption(httpLoader loader.Loader) Option {
|
||||
return func(opts *options) {
|
||||
opts.httpLoader = httpLoader
|
||||
}
|
||||
}
|
||||
|
||||
func LoggerOption(logger logger.Logger) Option {
|
||||
return func(opts *options) {
|
||||
opts.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
type localBypass struct {
|
||||
cidrMatcher matcher.Matcher
|
||||
addrMatcher matcher.Matcher
|
||||
wildcardMatcher matcher.Matcher
|
||||
ipRangeMatcher matcher.Matcher
|
||||
cancelFunc context.CancelFunc
|
||||
options options
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewBypass creates and initializes a new Bypass.
|
||||
// The rules will be reversed if the reverse option is true.
|
||||
func NewBypass(opts ...Option) bypass.Bypass {
|
||||
var options options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
|
||||
bp := &localBypass{
|
||||
cancelFunc: cancel,
|
||||
options: options,
|
||||
}
|
||||
|
||||
if err := bp.reload(ctx); err != nil {
|
||||
options.logger.Warnf("reload: %v", err)
|
||||
}
|
||||
if bp.options.period > 0 {
|
||||
go bp.periodReload(ctx)
|
||||
}
|
||||
|
||||
return bp
|
||||
}
|
||||
|
||||
func (bp *localBypass) periodReload(ctx context.Context) error {
|
||||
period := bp.options.period
|
||||
if period < time.Second {
|
||||
period = time.Second
|
||||
}
|
||||
ticker := time.NewTicker(period)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := bp.reload(ctx); err != nil {
|
||||
bp.options.logger.Warnf("reload: %v", err)
|
||||
// return err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *localBypass) reload(ctx context.Context) error {
|
||||
v, err := bp.load(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
patterns := append(bp.options.matchers, v...)
|
||||
bp.options.logger.Debugf("load items %d", len(patterns))
|
||||
|
||||
var addrs []string
|
||||
var inets []*net.IPNet
|
||||
var wildcards []string
|
||||
var ipRanges []xnet.IPRange
|
||||
for _, pattern := range patterns {
|
||||
if _, inet, err := net.ParseCIDR(pattern); err == nil {
|
||||
inets = append(inets, inet)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.ContainsAny(pattern, "*?") {
|
||||
wildcards = append(wildcards, pattern)
|
||||
continue
|
||||
}
|
||||
|
||||
r := xnet.IPRange{}
|
||||
if err := r.Parse(pattern); err == nil {
|
||||
ipRanges = append(ipRanges, r)
|
||||
continue
|
||||
}
|
||||
|
||||
addrs = append(addrs, pattern)
|
||||
}
|
||||
|
||||
bp.mu.Lock()
|
||||
defer bp.mu.Unlock()
|
||||
|
||||
bp.cidrMatcher = matcher.CIDRMatcher(inets)
|
||||
bp.addrMatcher = matcher.AddrMatcher(addrs)
|
||||
bp.wildcardMatcher = matcher.WildcardMatcher(wildcards)
|
||||
bp.ipRangeMatcher = matcher.IPRangeMatcher(ipRanges)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bp *localBypass) load(ctx context.Context) (patterns []string, err error) {
|
||||
if bp.options.fileLoader != nil {
|
||||
if lister, ok := bp.options.fileLoader.(loader.Lister); ok {
|
||||
list, er := lister.List(ctx)
|
||||
if er != nil {
|
||||
bp.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
for _, s := range list {
|
||||
if line := bp.parseLine(s); line != "" {
|
||||
patterns = append(patterns, line)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r, er := bp.options.fileLoader.Load(ctx)
|
||||
if er != nil {
|
||||
bp.options.logger.Warnf("file loader: %v", er)
|
||||
}
|
||||
if v, _ := bp.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if bp.options.redisLoader != nil {
|
||||
if lister, ok := bp.options.redisLoader.(loader.Lister); ok {
|
||||
list, er := lister.List(ctx)
|
||||
if er != nil {
|
||||
bp.options.logger.Warnf("redis loader: %v", er)
|
||||
}
|
||||
patterns = append(patterns, list...)
|
||||
} else {
|
||||
r, er := bp.options.redisLoader.Load(ctx)
|
||||
if er != nil {
|
||||
bp.options.logger.Warnf("redis loader: %v", er)
|
||||
}
|
||||
if v, _ := bp.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if bp.options.httpLoader != nil {
|
||||
r, er := bp.options.httpLoader.Load(ctx)
|
||||
if er != nil {
|
||||
bp.options.logger.Warnf("http loader: %v", er)
|
||||
}
|
||||
if v, _ := bp.parsePatterns(r); v != nil {
|
||||
patterns = append(patterns, v...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (bp *localBypass) parsePatterns(r io.Reader) (patterns []string, err error) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
if line := bp.parseLine(scanner.Text()); line != "" {
|
||||
patterns = append(patterns, line)
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
|
||||
func (bp *localBypass) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
|
||||
if addr == "" || bp == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
matched := bp.matched(addr)
|
||||
|
||||
b := !bp.options.whitelist && matched ||
|
||||
bp.options.whitelist && !matched
|
||||
if b {
|
||||
bp.options.logger.Debugf("bypass: %s, whitelist: %t", addr, bp.options.whitelist)
|
||||
} else {
|
||||
bp.options.logger.Debugf("pass: %s, whitelist: %t", addr, bp.options.whitelist)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *localBypass) IsWhitelist() bool {
|
||||
return p.options.whitelist
|
||||
}
|
||||
|
||||
func (bp *localBypass) parseLine(s string) string {
|
||||
if n := strings.IndexByte(s, '#'); n >= 0 {
|
||||
s = s[:n]
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func (bp *localBypass) matched(addr string) bool {
|
||||
bp.mu.RLock()
|
||||
defer bp.mu.RUnlock()
|
||||
|
||||
if bp.ipRangeMatcher.Match(addr) {
|
||||
return true
|
||||
}
|
||||
|
||||
if bp.addrMatcher.Match(addr) {
|
||||
return true
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(addr)
|
||||
if host == "" {
|
||||
host = addr
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil && bp.cidrMatcher.Match(host) {
|
||||
return true
|
||||
}
|
||||
|
||||
return bp.wildcardMatcher.Match(addr)
|
||||
}
|
||||
|
||||
func (bp *localBypass) Close() error {
|
||||
bp.cancelFunc()
|
||||
if bp.options.fileLoader != nil {
|
||||
bp.options.fileLoader.Close()
|
||||
}
|
||||
if bp.options.redisLoader != nil {
|
||||
bp.options.redisLoader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type bypassGroup struct {
|
||||
bypasses []bypass.Bypass
|
||||
}
|
||||
|
||||
func BypassGroup(bypasses ...bypass.Bypass) bypass.Bypass {
|
||||
return &bypassGroup{
|
||||
bypasses: bypasses,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *bypassGroup) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
|
||||
var whitelist, blacklist []bool
|
||||
for _, bypass := range p.bypasses {
|
||||
result := bypass.Contains(ctx, network, addr, opts...)
|
||||
if bypass.IsWhitelist() {
|
||||
whitelist = append(whitelist, result)
|
||||
} else {
|
||||
blacklist = append(blacklist, result)
|
||||
}
|
||||
}
|
||||
status := false
|
||||
if len(whitelist) > 0 {
|
||||
if slices.Contains(whitelist, false) {
|
||||
status = false
|
||||
} else {
|
||||
status = true
|
||||
}
|
||||
}
|
||||
if !status && len(blacklist) > 0 {
|
||||
if slices.Contains(blacklist, true) {
|
||||
status = true
|
||||
} else {
|
||||
status = false
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (p *bypassGroup) IsWhitelist() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package bypass
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/plugin/bypass/proto"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type grpcPlugin struct {
|
||||
conn grpc.ClientConnInterface
|
||||
client proto.BypassClient
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewGRPCPlugin creates a Bypass plugin based on gRPC.
|
||||
func NewGRPCPlugin(name string, addr string, opts ...plugin.Option) bypass.Bypass {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
log := logger.Default().WithFields(map[string]any{
|
||||
"kind": "bypass",
|
||||
"bypass": name,
|
||||
})
|
||||
conn, err := plugin.NewGRPCConn(addr, &options)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
p := &grpcPlugin{
|
||||
conn: conn,
|
||||
log: log,
|
||||
}
|
||||
if conn != nil {
|
||||
p.client = proto.NewBypassClient(conn)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) bool {
|
||||
if p.client == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
var options bypass.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
r, err := p.client.Bypass(ctx,
|
||||
&proto.BypassRequest{
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
Client: string(ctxvalue.ClientIDFromContext(ctx)),
|
||||
Host: options.Host,
|
||||
Path: options.Path,
|
||||
})
|
||||
if err != nil {
|
||||
p.log.Error(err)
|
||||
return true
|
||||
}
|
||||
return r.Ok
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) Close() error {
|
||||
if closer, ok := p.conn.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) IsWhitelist() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package bypass
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
type httpPluginRequest struct {
|
||||
Network string `json:"network"`
|
||||
Addr string `json:"addr"`
|
||||
Client string `json:"client"`
|
||||
Host string `json:"host"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type httpPluginResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
}
|
||||
|
||||
type httpPlugin struct {
|
||||
url string
|
||||
client *http.Client
|
||||
header http.Header
|
||||
log logger.Logger
|
||||
}
|
||||
|
||||
// NewHTTPPlugin creates an Bypass plugin based on HTTP.
|
||||
func NewHTTPPlugin(name string, url string, opts ...plugin.Option) bypass.Bypass {
|
||||
var options plugin.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &httpPlugin{
|
||||
url: url,
|
||||
client: plugin.NewHTTPClient(&options),
|
||||
header: options.Header,
|
||||
log: logger.Default().WithFields(map[string]any{
|
||||
"kind": "bypass",
|
||||
"bypass": name,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *httpPlugin) Contains(ctx context.Context, network, addr string, opts ...bypass.Option) (ok bool) {
|
||||
if p.client == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var options bypass.Options
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
rb := httpPluginRequest{
|
||||
Network: network,
|
||||
Addr: addr,
|
||||
Client: string(ctxvalue.ClientIDFromContext(ctx)),
|
||||
Host: options.Host,
|
||||
Path: options.Path,
|
||||
}
|
||||
v, err := json.Marshal(&rb)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.url, bytes.NewReader(v))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p.header != nil {
|
||||
req.Header = p.header.Clone()
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
res := httpPluginResponse{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
return
|
||||
}
|
||||
return res.OK
|
||||
}
|
||||
|
||||
func (p *httpPlugin) IsWhitelist() bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/hop"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/core/selector"
|
||||
)
|
||||
|
||||
var (
|
||||
_ chain.Chainer = (*chainGroup)(nil)
|
||||
)
|
||||
|
||||
type ChainOptions struct {
|
||||
Metadata metadata.Metadata
|
||||
Logger logger.Logger
|
||||
}
|
||||
|
||||
type ChainOption func(*ChainOptions)
|
||||
|
||||
func MetadataChainOption(md metadata.Metadata) ChainOption {
|
||||
return func(opts *ChainOptions) {
|
||||
opts.Metadata = md
|
||||
}
|
||||
}
|
||||
|
||||
func LoggerChainOption(logger logger.Logger) ChainOption {
|
||||
return func(opts *ChainOptions) {
|
||||
opts.Logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
type chainNamer interface {
|
||||
Name() string
|
||||
}
|
||||
|
||||
type Chain struct {
|
||||
name string
|
||||
hops []hop.Hop
|
||||
marker selector.Marker
|
||||
metadata metadata.Metadata
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewChain(name string, opts ...ChainOption) *Chain {
|
||||
var options ChainOptions
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
|
||||
return &Chain{
|
||||
name: name,
|
||||
metadata: options.Metadata,
|
||||
marker: selector.NewFailMarker(),
|
||||
logger: options.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chain) AddHop(hop hop.Hop) {
|
||||
c.hops = append(c.hops, hop)
|
||||
}
|
||||
|
||||
// Metadata implements metadata.Metadatable interface.
|
||||
func (c *Chain) Metadata() metadata.Metadata {
|
||||
return c.metadata
|
||||
}
|
||||
|
||||
// Marker implements selector.Markable interface.
|
||||
func (c *Chain) Marker() selector.Marker {
|
||||
return c.marker
|
||||
}
|
||||
|
||||
func (c *Chain) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *Chain) Route(ctx context.Context, network, address string, opts ...chain.RouteOption) chain.Route {
|
||||
if c == nil || len(c.hops) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var options chain.RouteOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
rt := NewRoute(ChainRouteOption(c))
|
||||
for _, h := range c.hops {
|
||||
node := h.Select(ctx,
|
||||
hop.NetworkSelectOption(network),
|
||||
hop.AddrSelectOption(address),
|
||||
hop.HostSelectOption(options.Host),
|
||||
)
|
||||
if node == nil {
|
||||
return rt
|
||||
}
|
||||
if node.Options().Transport.Multiplex() {
|
||||
tr := node.Options().Transport.Copy()
|
||||
tr.Options().Route = rt
|
||||
node = node.Copy()
|
||||
node.Options().Transport = tr
|
||||
rt = NewRoute(ChainRouteOption(c))
|
||||
}
|
||||
|
||||
rt.addNode(node)
|
||||
}
|
||||
return rt
|
||||
}
|
||||
|
||||
type chainGroup struct {
|
||||
chains []chain.Chainer
|
||||
selector selector.Selector[chain.Chainer]
|
||||
}
|
||||
|
||||
func NewChainGroup(chains ...chain.Chainer) *chainGroup {
|
||||
return &chainGroup{chains: chains}
|
||||
}
|
||||
|
||||
func (p *chainGroup) WithSelector(s selector.Selector[chain.Chainer]) *chainGroup {
|
||||
p.selector = s
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *chainGroup) Route(ctx context.Context, network, address string, opts ...chain.RouteOption) chain.Route {
|
||||
if chain := p.next(ctx); chain != nil {
|
||||
return chain.Route(ctx, network, address, opts...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *chainGroup) next(ctx context.Context) chain.Chainer {
|
||||
if p == nil || len(p.chains) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return p.selector.Select(ctx, p.chains...)
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/metrics"
|
||||
"github.com/go-gost/core/selector"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
"github.com/go-gost/x/internal/net/dialer"
|
||||
"github.com/go-gost/x/internal/net/udp"
|
||||
xmetrics "github.com/go-gost/x/metrics"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyRoute = errors.New("empty route")
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultRoute chain.Route = &defaultRoute{}
|
||||
)
|
||||
|
||||
// defaultRoute is a Route without nodes.
|
||||
type defaultRoute struct{}
|
||||
|
||||
func (*defaultRoute) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
||||
var options chain.DialOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
netd := dialer.Dialer{
|
||||
Interface: options.Interface,
|
||||
Netns: options.Netns,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
if options.SockOpts != nil {
|
||||
netd.Mark = options.SockOpts.Mark
|
||||
}
|
||||
|
||||
return netd.Dial(ctx, network, address)
|
||||
}
|
||||
|
||||
func (*defaultRoute) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
||||
var options chain.BindOptions
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
addr, err := net.ResolveTCPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.ListenTCP(network, addr)
|
||||
case "udp", "udp4", "udp6":
|
||||
addr, err := net.ResolveUDPAddr(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.ListenUDP(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger := logger.Default().WithFields(map[string]any{
|
||||
"network": network,
|
||||
"address": address,
|
||||
})
|
||||
ln := udp.NewListener(conn, &udp.ListenConfig{
|
||||
Backlog: options.Backlog,
|
||||
ReadQueueSize: options.UDPDataQueueSize,
|
||||
ReadBufferSize: options.UDPDataBufferSize,
|
||||
TTL: options.UDPConnTTL,
|
||||
Keepalive: true,
|
||||
Logger: logger,
|
||||
})
|
||||
return ln, err
|
||||
default:
|
||||
err := fmt.Errorf("network %s unsupported", network)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (r *defaultRoute) Nodes() []*chain.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RouteOptions struct {
|
||||
Chain chain.Chainer
|
||||
}
|
||||
|
||||
type RouteOption func(*RouteOptions)
|
||||
|
||||
func ChainRouteOption(c chain.Chainer) RouteOption {
|
||||
return func(o *RouteOptions) {
|
||||
o.Chain = c
|
||||
}
|
||||
}
|
||||
|
||||
type chainRoute struct {
|
||||
nodes []*chain.Node
|
||||
options RouteOptions
|
||||
}
|
||||
|
||||
func NewRoute(opts ...RouteOption) *chainRoute {
|
||||
var options RouteOptions
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
|
||||
return &chainRoute{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *chainRoute) addNode(nodes ...*chain.Node) {
|
||||
r.nodes = append(r.nodes, nodes...)
|
||||
}
|
||||
|
||||
func (r *chainRoute) Dial(ctx context.Context, network, address string, opts ...chain.DialOption) (net.Conn, error) {
|
||||
if len(r.Nodes()) == 0 {
|
||||
return DefaultRoute.Dial(ctx, network, address, opts...)
|
||||
}
|
||||
|
||||
var options chain.DialOptions
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
conn, err := r.connect(ctx, options.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc, err := r.getNode(len(r.Nodes())-1).Options().Transport.Connect(ctx, conn, network, address)
|
||||
if err != nil {
|
||||
if conn != nil {
|
||||
conn.Close()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (r *chainRoute) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (net.Listener, error) {
|
||||
if len(r.Nodes()) == 0 {
|
||||
return DefaultRoute.Bind(ctx, network, address, opts...)
|
||||
}
|
||||
|
||||
var options chain.BindOptions
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&options)
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := r.connect(ctx, options.Logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln, err := r.getNode(len(r.Nodes())-1).Options().Transport.Bind(ctx,
|
||||
conn, network, address,
|
||||
connector.BacklogBindOption(options.Backlog),
|
||||
connector.MuxBindOption(options.Mux),
|
||||
connector.UDPConnTTLBindOption(options.UDPConnTTL),
|
||||
connector.UDPDataBufferSizeBindOption(options.UDPDataBufferSize),
|
||||
connector.UDPDataQueueSizeBindOption(options.UDPDataQueueSize),
|
||||
)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (r *chainRoute) connect(ctx context.Context, logger logger.Logger) (conn net.Conn, err error) {
|
||||
network := "ip"
|
||||
node := r.nodes[0]
|
||||
|
||||
defer func() {
|
||||
if r.options.Chain != nil {
|
||||
var marker selector.Marker
|
||||
if m, ok := r.options.Chain.(selector.Markable); ok && m != nil {
|
||||
marker = m.Marker()
|
||||
}
|
||||
var name string
|
||||
if cn, _ := r.options.Chain.(chainNamer); cn != nil {
|
||||
name = cn.Name()
|
||||
}
|
||||
// chain error
|
||||
if err != nil {
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
if v := xmetrics.GetCounter(xmetrics.MetricChainErrorsCounter,
|
||||
metrics.Labels{"chain": name, "node": node.Name}); v != nil {
|
||||
v.Inc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if marker != nil {
|
||||
marker.Reset()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
addr, err := xnet.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
||||
marker := node.Marker()
|
||||
if err != nil {
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
cc, err := node.Options().Transport.Dial(ctx, addr)
|
||||
if err != nil {
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cn, err := node.Options().Transport.Handshake(ctx, cc)
|
||||
if err != nil {
|
||||
cc.Close()
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
if marker != nil {
|
||||
marker.Reset()
|
||||
}
|
||||
|
||||
if r.options.Chain != nil {
|
||||
var name string
|
||||
if cn, _ := r.options.Chain.(chainNamer); cn != nil {
|
||||
name = cn.Name()
|
||||
}
|
||||
if v := xmetrics.GetObserver(xmetrics.MetricNodeConnectDurationObserver,
|
||||
metrics.Labels{"chain": name, "node": node.Name}); v != nil {
|
||||
v.Observe(time.Since(start).Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
preNode := node
|
||||
for _, node := range r.nodes[1:] {
|
||||
marker := node.Marker()
|
||||
addr, err = xnet.Resolve(ctx, network, node.Addr, node.Options().Resolver, node.Options().HostMapper, logger)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
cc, err = preNode.Options().Transport.Connect(ctx, cn, "tcp", addr)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
cc, err = node.Options().Transport.Handshake(ctx, cc)
|
||||
if err != nil {
|
||||
cn.Close()
|
||||
if marker != nil {
|
||||
marker.Mark()
|
||||
}
|
||||
return
|
||||
}
|
||||
if marker != nil {
|
||||
marker.Reset()
|
||||
}
|
||||
|
||||
cn = cc
|
||||
preNode = node
|
||||
}
|
||||
|
||||
conn = cn
|
||||
return
|
||||
}
|
||||
|
||||
func (r *chainRoute) getNode(index int) *chain.Node {
|
||||
if r == nil || len(r.Nodes()) == 0 || index < 0 || index >= len(r.Nodes()) {
|
||||
return nil
|
||||
}
|
||||
return r.nodes[index]
|
||||
}
|
||||
|
||||
func (r *chainRoute) Nodes() []*chain.Node {
|
||||
if r != nil {
|
||||
return r.nodes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/recorder"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
options chain.RouterOptions
|
||||
}
|
||||
|
||||
func NewRouter(opts ...chain.RouterOption) *Router {
|
||||
r := &Router{}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&r.options)
|
||||
}
|
||||
}
|
||||
if r.options.Timeout == 0 {
|
||||
r.options.Timeout = 15 * time.Second
|
||||
}
|
||||
|
||||
if r.options.Logger == nil {
|
||||
r.options.Logger = logger.Default().WithFields(map[string]any{"kind": "router"})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Router) Options() *chain.RouterOptions {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return &r.options
|
||||
}
|
||||
|
||||
func (r *Router) Dial(ctx context.Context, network, address string) (conn net.Conn, err error) {
|
||||
host := address
|
||||
if h, _, _ := net.SplitHostPort(address); h != "" {
|
||||
host = h
|
||||
}
|
||||
r.record(ctx, recorder.RecorderServiceRouterDialAddress, []byte(host))
|
||||
|
||||
log := r.options.Logger.WithFields(map[string]any{
|
||||
"sid": ctxvalue.SidFromContext(ctx),
|
||||
})
|
||||
|
||||
conn, err = r.dial(ctx, network, address, log)
|
||||
if err != nil {
|
||||
r.record(ctx, recorder.RecorderServiceRouterDialAddressError, []byte(host))
|
||||
return
|
||||
}
|
||||
|
||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||
if _, ok := conn.(net.PacketConn); !ok {
|
||||
return &packetConn{conn}, nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Router) record(ctx context.Context, name string, data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, rec := range r.options.Recorders {
|
||||
if rec.Record == name {
|
||||
return rec.Recorder.Record(ctx, data)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) dial(ctx context.Context, network, address string, log logger.Logger) (conn net.Conn, err error) {
|
||||
count := r.options.Retries + 1
|
||||
if count <= 0 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
log.Debugf("dial %s/%s", address, network)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
ctx := ctx
|
||||
if r.options.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
buf := ctxvalue.BufferFromContext(ctx)
|
||||
if buf != nil {
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
var ipAddr string
|
||||
ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, log)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
break
|
||||
}
|
||||
|
||||
if buf != nil {
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
var route chain.Route
|
||||
if r.options.Chain != nil {
|
||||
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
buf = &bytes.Buffer{}
|
||||
}
|
||||
for _, node := range routePath(route) {
|
||||
fmt.Fprintf(buf, "%s@%s > ", node.Name, node.Addr)
|
||||
}
|
||||
fmt.Fprintf(buf, "%s", ipAddr)
|
||||
log.Debugf("route(retry=%d) %s", i, buf.String())
|
||||
|
||||
if route == nil {
|
||||
route = DefaultRoute
|
||||
}
|
||||
conn, err = route.Dial(ctx, network, ipAddr,
|
||||
chain.InterfaceDialOption(r.options.IfceName),
|
||||
chain.NetnsDialOption(r.options.Netns),
|
||||
chain.SockOptsDialOption(r.options.SockOpts),
|
||||
chain.LoggerDialOption(log),
|
||||
)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
log.Errorf("route(retry=%d) %s", i, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Router) Bind(ctx context.Context, network, address string, opts ...chain.BindOption) (ln net.Listener, err error) {
|
||||
count := r.options.Retries + 1
|
||||
if count <= 0 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
log := r.options.Logger.WithFields(map[string]any{
|
||||
"sid": ctxvalue.SidFromContext(ctx),
|
||||
})
|
||||
|
||||
log.Debugf("bind on %s/%s", address, network)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
ctx := ctx
|
||||
if r.options.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
var route chain.Route
|
||||
if r.options.Chain != nil {
|
||||
route = r.options.Chain.Route(ctx, network, address)
|
||||
if route == nil || len(route.Nodes()) == 0 {
|
||||
err = ErrEmptyRoute
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.DebugLevel) {
|
||||
buf := bytes.Buffer{}
|
||||
for _, node := range routePath(route) {
|
||||
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s", address)
|
||||
log.Debugf("route(retry=%d) %s", i, buf.String())
|
||||
}
|
||||
|
||||
if route == nil {
|
||||
route = DefaultRoute
|
||||
}
|
||||
ln, err = route.Bind(ctx, network, address, opts...)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
log.Errorf("route(retry=%d) %s", i, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func routePath(route chain.Route) (path []*chain.Node) {
|
||||
if route == nil {
|
||||
return
|
||||
}
|
||||
for _, node := range route.Nodes() {
|
||||
if tr := node.Options().Transport; tr != nil {
|
||||
path = append(path, routePath(tr.Options().Route)...)
|
||||
}
|
||||
path = append(path, node)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *packetConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
n, err = c.Read(b)
|
||||
addr = c.Conn.RemoteAddr()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
return c.Write(b)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/dialer"
|
||||
net_dialer "github.com/go-gost/x/internal/net/dialer"
|
||||
)
|
||||
|
||||
type Transport struct {
|
||||
dialer dialer.Dialer
|
||||
connector connector.Connector
|
||||
options chain.TransportOptions
|
||||
}
|
||||
|
||||
func NewTransport(d dialer.Dialer, c connector.Connector, opts ...chain.TransportOption) *Transport {
|
||||
tr := &Transport{
|
||||
dialer: d,
|
||||
connector: c,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&tr.options)
|
||||
}
|
||||
}
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
func (tr *Transport) Dial(ctx context.Context, addr string) (net.Conn, error) {
|
||||
netd := &net_dialer.Dialer{
|
||||
Interface: tr.options.IfceName,
|
||||
Netns: tr.options.Netns,
|
||||
}
|
||||
if tr.options.SockOpts != nil {
|
||||
netd.Mark = tr.options.SockOpts.Mark
|
||||
}
|
||||
if tr.options.Route != nil && len(tr.options.Route.Nodes()) > 0 {
|
||||
netd.DialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return tr.options.Route.Dial(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
opts := []dialer.DialOption{
|
||||
dialer.HostDialOption(tr.options.Addr),
|
||||
dialer.NetDialerDialOption(netd),
|
||||
}
|
||||
return tr.dialer.Dial(ctx, addr, opts...)
|
||||
}
|
||||
|
||||
func (tr *Transport) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||
var err error
|
||||
if hs, ok := tr.dialer.(dialer.Handshaker); ok {
|
||||
conn, err = hs.Handshake(ctx, conn,
|
||||
dialer.AddrHandshakeOption(tr.options.Addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if hs, ok := tr.connector.(connector.Handshaker); ok {
|
||||
return hs.Handshake(ctx, conn)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (tr *Transport) Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error) {
|
||||
netd := &net_dialer.Dialer{
|
||||
Interface: tr.options.IfceName,
|
||||
Netns: tr.options.Netns,
|
||||
}
|
||||
if tr.options.SockOpts != nil {
|
||||
netd.Mark = tr.options.SockOpts.Mark
|
||||
}
|
||||
return tr.connector.Connect(ctx, conn, network, address,
|
||||
connector.DialerConnectOption(netd),
|
||||
)
|
||||
}
|
||||
|
||||
func (tr *Transport) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
||||
if binder, ok := tr.connector.(connector.Binder); ok {
|
||||
return binder.Bind(ctx, conn, network, address, opts...)
|
||||
}
|
||||
return nil, connector.ErrBindUnsupported
|
||||
}
|
||||
|
||||
func (tr *Transport) Multiplex() bool {
|
||||
if mux, ok := tr.dialer.(dialer.Multiplexer); ok {
|
||||
return mux.Multiplex()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (tr *Transport) Options() *chain.TransportOptions {
|
||||
if tr != nil {
|
||||
return &tr.options
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tr *Transport) Copy() chain.Transporter {
|
||||
tr2 := &Transport{}
|
||||
*tr2 = *tr
|
||||
return tr
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/x/config"
|
||||
xnet "github.com/go-gost/x/internal/net"
|
||||
"github.com/go-gost/x/limiter/conn"
|
||||
"github.com/go-gost/x/limiter/traffic"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidCmd = errors.New("invalid cmd")
|
||||
ErrInvalidNode = errors.New("invalid node")
|
||||
)
|
||||
|
||||
func BuildConfigFromCmd(serviceList, nodeList []string) (*config.Config, error) {
|
||||
namePrefix := ""
|
||||
cfg := &config.Config{}
|
||||
|
||||
var chain *config.ChainConfig
|
||||
if len(nodeList) > 0 {
|
||||
chain = &config.ChainConfig{
|
||||
Name: fmt.Sprintf("%schain-0", namePrefix),
|
||||
}
|
||||
cfg.Chains = append(cfg.Chains, chain)
|
||||
}
|
||||
|
||||
for i, node := range nodeList {
|
||||
url, err := Norm(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeConfig, err := buildNodeConfig(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeConfig.Name = fmt.Sprintf("%snode-0", namePrefix)
|
||||
|
||||
var nodes []*config.NodeConfig
|
||||
for _, host := range strings.Split(nodeConfig.Addr, ",") {
|
||||
if host == "" {
|
||||
continue
|
||||
}
|
||||
nodeCfg := &config.NodeConfig{}
|
||||
*nodeCfg = *nodeConfig
|
||||
nodeCfg.Name = fmt.Sprintf("%snode-%d", namePrefix, len(nodes))
|
||||
nodeCfg.Addr = host
|
||||
nodes = append(nodes, nodeCfg)
|
||||
}
|
||||
|
||||
m := map[string]any{}
|
||||
for k, v := range url.Query() {
|
||||
if len(v) > 0 {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
md := mdx.NewMetadata(m)
|
||||
|
||||
hopConfig := &config.HopConfig{
|
||||
Name: fmt.Sprintf("%shop-%d", namePrefix, i),
|
||||
Selector: parseSelector(m),
|
||||
Nodes: nodes,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
if v := mdutil.GetString(md, "bypass"); v != "" {
|
||||
bypassCfg := &config.BypassConfig{
|
||||
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
|
||||
}
|
||||
if v[0] == '~' {
|
||||
bypassCfg.Whitelist = true
|
||||
v = v[1:]
|
||||
}
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
||||
}
|
||||
hopConfig.Bypass = bypassCfg.Name
|
||||
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
||||
delete(m, "bypass")
|
||||
}
|
||||
if v := mdutil.GetString(md, "resolver"); v != "" {
|
||||
resolverCfg := &config.ResolverConfig{
|
||||
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
|
||||
}
|
||||
for _, rs := range strings.Split(v, ",") {
|
||||
if rs == "" {
|
||||
continue
|
||||
}
|
||||
resolverCfg.Nameservers = append(
|
||||
resolverCfg.Nameservers,
|
||||
&config.NameserverConfig{
|
||||
Addr: rs,
|
||||
},
|
||||
)
|
||||
}
|
||||
hopConfig.Resolver = resolverCfg.Name
|
||||
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
||||
delete(m, "resolver")
|
||||
}
|
||||
if v := mdutil.GetString(md, "hosts"); v != "" {
|
||||
hostsCfg := &config.HostsConfig{
|
||||
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
|
||||
}
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
ss := strings.SplitN(s, ":", 2)
|
||||
if len(ss) != 2 {
|
||||
continue
|
||||
}
|
||||
hostsCfg.Mappings = append(
|
||||
hostsCfg.Mappings,
|
||||
&config.HostMappingConfig{
|
||||
Hostname: ss[0],
|
||||
IP: ss[1],
|
||||
},
|
||||
)
|
||||
}
|
||||
hopConfig.Hosts = hostsCfg.Name
|
||||
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
||||
delete(m, "hosts")
|
||||
}
|
||||
|
||||
if v := mdutil.GetString(md, "interface"); v != "" {
|
||||
hopConfig.Interface = v
|
||||
delete(m, "interface")
|
||||
}
|
||||
if v := mdutil.GetInt(md, "so_mark"); v > 0 {
|
||||
hopConfig.SockOpts = &config.SockOptsConfig{
|
||||
Mark: v,
|
||||
}
|
||||
delete(m, "so_mark")
|
||||
}
|
||||
|
||||
chain.Hops = append(chain.Hops, hopConfig)
|
||||
}
|
||||
|
||||
var services []*config.ServiceConfig
|
||||
for _, svc := range serviceList {
|
||||
svc = strings.TrimSpace(svc)
|
||||
if svc == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if svc[0] == ':' || !strings.Contains(svc, "://") {
|
||||
svc = "auto://" + svc
|
||||
}
|
||||
|
||||
host, svc := cutHost(svc)
|
||||
|
||||
url, err := Norm(svc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
url.Host = host
|
||||
|
||||
svcs, err := buildServiceConfig(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, svcs...)
|
||||
}
|
||||
|
||||
for i, service := range services {
|
||||
service.Name = fmt.Sprintf("%sservice-%d", namePrefix, i)
|
||||
if chain != nil {
|
||||
if service.Listener.Type == "rtcp" || service.Listener.Type == "rudp" {
|
||||
service.Listener.Chain = chain.Name
|
||||
} else {
|
||||
service.Handler.Chain = chain.Name
|
||||
}
|
||||
}
|
||||
cfg.Services = append(cfg.Services, service)
|
||||
|
||||
mh := service.Handler.Metadata
|
||||
md := mdx.NewMetadata(mh)
|
||||
if v := mdutil.GetInt(md, "retries"); v > 0 {
|
||||
service.Handler.Retries = v
|
||||
delete(mh, "retries")
|
||||
}
|
||||
if v := mdutil.GetString(md, "admission"); v != "" {
|
||||
admCfg := &config.AdmissionConfig{
|
||||
Name: fmt.Sprintf("%sadmission-%d", namePrefix, len(cfg.Admissions)),
|
||||
}
|
||||
if v[0] == '~' {
|
||||
admCfg.Whitelist = true
|
||||
v = v[1:]
|
||||
}
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
admCfg.Matchers = append(admCfg.Matchers, s)
|
||||
}
|
||||
service.Admission = admCfg.Name
|
||||
cfg.Admissions = append(cfg.Admissions, admCfg)
|
||||
delete(mh, "admission")
|
||||
}
|
||||
if v := mdutil.GetString(md, "bypass"); v != "" {
|
||||
bypassCfg := &config.BypassConfig{
|
||||
Name: fmt.Sprintf("%sbypass-%d", namePrefix, len(cfg.Bypasses)),
|
||||
}
|
||||
if v[0] == '~' {
|
||||
bypassCfg.Whitelist = true
|
||||
v = v[1:]
|
||||
}
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
bypassCfg.Matchers = append(bypassCfg.Matchers, s)
|
||||
}
|
||||
service.Bypass = bypassCfg.Name
|
||||
cfg.Bypasses = append(cfg.Bypasses, bypassCfg)
|
||||
delete(mh, "bypass")
|
||||
}
|
||||
if v := mdutil.GetString(md, "resolver"); v != "" {
|
||||
resolverCfg := &config.ResolverConfig{
|
||||
Name: fmt.Sprintf("%sresolver-%d", namePrefix, len(cfg.Resolvers)),
|
||||
}
|
||||
for _, rs := range strings.Split(v, ",") {
|
||||
if rs == "" {
|
||||
continue
|
||||
}
|
||||
resolverCfg.Nameservers = append(
|
||||
resolverCfg.Nameservers,
|
||||
&config.NameserverConfig{
|
||||
Addr: rs,
|
||||
Prefer: mdutil.GetString(md, "prefer"),
|
||||
},
|
||||
)
|
||||
}
|
||||
service.Resolver = resolverCfg.Name
|
||||
cfg.Resolvers = append(cfg.Resolvers, resolverCfg)
|
||||
delete(mh, "resolver")
|
||||
}
|
||||
if v := mdutil.GetString(md, "hosts"); v != "" {
|
||||
hostsCfg := &config.HostsConfig{
|
||||
Name: fmt.Sprintf("%shosts-%d", namePrefix, len(cfg.Hosts)),
|
||||
}
|
||||
for _, s := range strings.Split(v, ",") {
|
||||
ss := strings.SplitN(s, ":", 2)
|
||||
if len(ss) != 2 {
|
||||
continue
|
||||
}
|
||||
hostsCfg.Mappings = append(
|
||||
hostsCfg.Mappings,
|
||||
&config.HostMappingConfig{
|
||||
Hostname: ss[0],
|
||||
IP: ss[1],
|
||||
},
|
||||
)
|
||||
}
|
||||
service.Hosts = hostsCfg.Name
|
||||
cfg.Hosts = append(cfg.Hosts, hostsCfg)
|
||||
delete(mh, "hosts")
|
||||
}
|
||||
|
||||
in := mdutil.GetString(md, "limiter.in")
|
||||
out := mdutil.GetString(md, "limiter.out")
|
||||
cin := mdutil.GetString(md, "limiter.conn.in")
|
||||
cout := mdutil.GetString(md, "limiter.conn.out")
|
||||
if in != "" || cin != "" || out != "" || cout != "" {
|
||||
limiter := &config.LimiterConfig{
|
||||
Name: fmt.Sprintf("%slimiter-%d", namePrefix, len(cfg.Limiters)),
|
||||
}
|
||||
if in != "" || out != "" {
|
||||
limiter.Limits = append(limiter.Limits,
|
||||
fmt.Sprintf("%s %s %s", traffic.ServiceLimitKey, in, out))
|
||||
}
|
||||
if cin != "" || cout != "" {
|
||||
limiter.Limits = append(limiter.Limits,
|
||||
fmt.Sprintf("%s %s %s", traffic.ConnLimitKey, cin, cout))
|
||||
}
|
||||
service.Limiter = limiter.Name
|
||||
cfg.Limiters = append(cfg.Limiters, limiter)
|
||||
delete(mh, "limiter.in")
|
||||
delete(mh, "limiter.out")
|
||||
delete(mh, "limiter.conn.in")
|
||||
delete(mh, "limiter.conn.out")
|
||||
}
|
||||
|
||||
if climit := mdutil.GetInt(md, "climiter"); climit > 0 {
|
||||
limiter := &config.LimiterConfig{
|
||||
Name: fmt.Sprintf("%sclimiter-%d", namePrefix, len(cfg.CLimiters)),
|
||||
Limits: []string{fmt.Sprintf("%s %d", conn.GlobalLimitKey, climit)},
|
||||
}
|
||||
service.CLimiter = limiter.Name
|
||||
cfg.CLimiters = append(cfg.CLimiters, limiter)
|
||||
delete(mh, "climiter")
|
||||
}
|
||||
|
||||
if rlimit := mdutil.GetFloat(md, "rlimiter"); rlimit > 0 {
|
||||
limiter := &config.LimiterConfig{
|
||||
Name: fmt.Sprintf("%srlimiter-%d", namePrefix, len(cfg.RLimiters)),
|
||||
Limits: []string{fmt.Sprintf("%s %s", conn.GlobalLimitKey, strconv.FormatFloat(rlimit, 'f', -1, 64))},
|
||||
}
|
||||
service.RLimiter = limiter.Name
|
||||
cfg.RLimiters = append(cfg.RLimiters, limiter)
|
||||
delete(mh, "rlimiter")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func cutHost(s string) (host, remain string) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
n := strings.IndexByte(s, ':')
|
||||
start := n + 3
|
||||
end := strings.IndexAny(s[start:], "/?")
|
||||
if end < 0 {
|
||||
end = len(s)
|
||||
} else {
|
||||
end += start
|
||||
}
|
||||
// auth info
|
||||
if n = strings.LastIndexByte(s[start:end], '@'); n >= 0 {
|
||||
start += (n + 1)
|
||||
}
|
||||
host = s[start:end]
|
||||
|
||||
remain = s[:start] + s[end:]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func buildServiceConfig(url *url.URL) ([]*config.ServiceConfig, error) {
|
||||
namePrefix := ""
|
||||
if v := os.Getenv("_GOST_ID"); v != "" {
|
||||
namePrefix = fmt.Sprintf("go-%s@", v)
|
||||
}
|
||||
|
||||
var handler, listener string
|
||||
schemes := strings.Split(url.Scheme, "+")
|
||||
if len(schemes) == 1 {
|
||||
handler = schemes[0]
|
||||
listener = schemes[0]
|
||||
}
|
||||
if len(schemes) == 2 {
|
||||
handler = schemes[0]
|
||||
listener = schemes[1]
|
||||
}
|
||||
|
||||
addrs := xnet.AddrPortRange(url.Host).Addrs()
|
||||
if len(addrs) == 0 {
|
||||
addrs = append(addrs, url.Host)
|
||||
}
|
||||
|
||||
var services []*config.ServiceConfig
|
||||
for _, addr := range addrs {
|
||||
services = append(services, &config.ServiceConfig{
|
||||
Addr: addr,
|
||||
})
|
||||
}
|
||||
|
||||
if h := registry.HandlerRegistry().Get(handler); h == nil {
|
||||
handler = "auto"
|
||||
}
|
||||
if ln := registry.ListenerRegistry().Get(listener); ln == nil {
|
||||
listener = "tcp"
|
||||
if handler == "ssu" {
|
||||
listener = "udp"
|
||||
}
|
||||
}
|
||||
|
||||
var nodes []*config.ForwardNodeConfig
|
||||
// forward mode
|
||||
if remotes := strings.Trim(url.EscapedPath(), "/"); remotes != "" {
|
||||
i := 0
|
||||
for _, addr := range strings.Split(remotes, ",") {
|
||||
addrs := xnet.AddrPortRange(addr).Addrs()
|
||||
if len(addrs) == 0 {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
for _, adr := range addrs {
|
||||
nodes = append(nodes, &config.ForwardNodeConfig{
|
||||
Name: fmt.Sprintf("%starget-%d", namePrefix, i),
|
||||
Addr: adr,
|
||||
})
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
if handler != "relay" {
|
||||
if listener == "tcp" || listener == "udp" ||
|
||||
listener == "rtcp" || listener == "rudp" ||
|
||||
listener == "tun" || listener == "tap" ||
|
||||
listener == "dns" || listener == "unix" ||
|
||||
listener == "serial" {
|
||||
handler = listener
|
||||
} else {
|
||||
handler = "forward"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodes) > 0 {
|
||||
if len(services) == 1 {
|
||||
services[0].Forwarder = &config.ForwarderConfig{
|
||||
Nodes: nodes,
|
||||
}
|
||||
} else {
|
||||
for i, svc := range services {
|
||||
if len(nodes) == 1 {
|
||||
svc.Forwarder = &config.ForwarderConfig{
|
||||
Nodes: nodes,
|
||||
}
|
||||
} else {
|
||||
if i < len(nodes) {
|
||||
svc.Forwarder = &config.ForwarderConfig{
|
||||
Nodes: []*config.ForwardNodeConfig{nodes[i]},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var auth *config.AuthConfig
|
||||
if url.User != nil {
|
||||
auth = &config.AuthConfig{
|
||||
Username: url.User.Username(),
|
||||
}
|
||||
auth.Password, _ = url.User.Password()
|
||||
}
|
||||
|
||||
m := map[string]any{}
|
||||
for k, v := range url.Query() {
|
||||
if len(v) > 0 {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
md := mdx.NewMetadata(m)
|
||||
|
||||
if sa := mdutil.GetString(md, "auth"); sa != "" {
|
||||
au, err := parseAuthFromCmd(sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth = au
|
||||
}
|
||||
delete(m, "auth")
|
||||
|
||||
tlsConfig := &config.TLSConfig{
|
||||
CertFile: mdutil.GetString(md, "tls.certFile", "certFile", "cert"),
|
||||
KeyFile: mdutil.GetString(md, "tls.keyFile", "keyFile", "key"),
|
||||
CAFile: mdutil.GetString(md, "tls.caFile", "caFile", "ca"),
|
||||
}
|
||||
|
||||
delete(m, "tls.certFile")
|
||||
delete(m, "certFile")
|
||||
delete(m, "cert")
|
||||
delete(m, "tls.keyFile")
|
||||
delete(m, "keyFile")
|
||||
delete(m, "key")
|
||||
delete(m, "tls.caFile")
|
||||
delete(m, "caFile")
|
||||
delete(m, "ca")
|
||||
|
||||
if tlsConfig.CertFile == "" {
|
||||
tlsConfig = nil
|
||||
}
|
||||
|
||||
if v := mdutil.GetString(md, "dns"); v != "" {
|
||||
md.Set("dns", strings.Split(v, ","))
|
||||
}
|
||||
|
||||
selector := parseSelector(m)
|
||||
handlerCfg := &config.HandlerConfig{
|
||||
Type: handler,
|
||||
Auth: auth,
|
||||
Metadata: m,
|
||||
}
|
||||
listenerCfg := &config.ListenerConfig{
|
||||
Type: listener,
|
||||
TLS: tlsConfig,
|
||||
Metadata: m,
|
||||
}
|
||||
if listenerCfg.Type == "ssh" || listenerCfg.Type == "sshd" {
|
||||
handlerCfg.Auth = nil
|
||||
listenerCfg.Auth = auth
|
||||
}
|
||||
|
||||
for _, svc := range services {
|
||||
if svc.Forwarder != nil {
|
||||
svc.Forwarder.Selector = selector
|
||||
}
|
||||
svc.Handler = handlerCfg
|
||||
svc.Listener = listenerCfg
|
||||
svc.Metadata = m
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func buildNodeConfig(url *url.URL) (*config.NodeConfig, error) {
|
||||
var connector, dialer string
|
||||
schemes := strings.Split(url.Scheme, "+")
|
||||
if len(schemes) == 1 {
|
||||
connector = schemes[0]
|
||||
dialer = schemes[0]
|
||||
}
|
||||
if len(schemes) == 2 {
|
||||
connector = schemes[0]
|
||||
dialer = schemes[1]
|
||||
}
|
||||
|
||||
m := map[string]any{}
|
||||
for k, v := range url.Query() {
|
||||
if len(v) > 0 {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
md := mdx.NewMetadata(m)
|
||||
|
||||
node := &config.NodeConfig{
|
||||
Addr: url.Host,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
if c := registry.ConnectorRegistry().Get(connector); c == nil {
|
||||
connector = "http"
|
||||
}
|
||||
if d := registry.DialerRegistry().Get(dialer); d == nil {
|
||||
dialer = "tcp"
|
||||
if connector == "ssu" {
|
||||
dialer = "udp"
|
||||
}
|
||||
}
|
||||
|
||||
var auth *config.AuthConfig
|
||||
if url.User != nil {
|
||||
auth = &config.AuthConfig{
|
||||
Username: url.User.Username(),
|
||||
}
|
||||
auth.Password, _ = url.User.Password()
|
||||
}
|
||||
|
||||
if sauth := mdutil.GetString(md, "auth"); sauth != "" && auth == nil {
|
||||
au, err := parseAuthFromCmd(sauth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth = au
|
||||
}
|
||||
delete(m, "auth")
|
||||
|
||||
tlsConfig := &config.TLSConfig{
|
||||
CertFile: mdutil.GetString(md, "tls.certFile", "certFile", "cert"),
|
||||
KeyFile: mdutil.GetString(md, "tls.keyFile", "keyFile", "key"),
|
||||
CAFile: mdutil.GetString(md, "tls.caFile", "caFile", "ca"),
|
||||
Secure: mdutil.GetBool(md, "tls.secure", "secure"),
|
||||
ServerName: mdutil.GetString(md, "tls.servername", "servername"),
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig.ServerName = url.Hostname()
|
||||
}
|
||||
|
||||
delete(m, "tls.certFile")
|
||||
delete(m, "certFile")
|
||||
delete(m, "cert")
|
||||
delete(m, "tls.keyFile")
|
||||
delete(m, "keyFile")
|
||||
delete(m, "key")
|
||||
delete(m, "tls.caFile")
|
||||
delete(m, "caFile")
|
||||
delete(m, "ca")
|
||||
delete(m, "tls.secure")
|
||||
delete(m, "secure")
|
||||
delete(m, "tls.servername")
|
||||
delete(m, "serverName")
|
||||
|
||||
if !tlsConfig.Secure && tlsConfig.CertFile == "" && tlsConfig.CAFile == "" && tlsConfig.ServerName == "" {
|
||||
tlsConfig = nil
|
||||
}
|
||||
|
||||
node.Connector = &config.ConnectorConfig{
|
||||
Type: connector,
|
||||
Auth: auth,
|
||||
Metadata: m,
|
||||
}
|
||||
node.Dialer = &config.DialerConfig{
|
||||
Type: dialer,
|
||||
TLS: tlsConfig,
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
if node.Dialer.Type == "ssh" || node.Dialer.Type == "sshd" {
|
||||
node.Connector.Auth = nil
|
||||
node.Dialer.Auth = auth
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func Norm(s string) (*url.URL, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return nil, ErrInvalidCmd
|
||||
}
|
||||
|
||||
if s[0] == ':' || !strings.Contains(s, "://") {
|
||||
s = "auto://" + s
|
||||
}
|
||||
|
||||
url, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if url.Scheme == "https" {
|
||||
url.Scheme = "http+tls"
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func parseAuthFromCmd(sa string) (*config.AuthConfig, error) {
|
||||
v, err := base64.StdEncoding.DecodeString(sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := string(v)
|
||||
n := strings.IndexByte(cs, ':')
|
||||
if n < 0 {
|
||||
return &config.AuthConfig{
|
||||
Username: cs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &config.AuthConfig{
|
||||
Username: cs[:n],
|
||||
Password: cs[n+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseSelector(m map[string]any) *config.SelectorConfig {
|
||||
md := mdx.NewMetadata(m)
|
||||
strategy := mdutil.GetString(md, "strategy")
|
||||
maxFails := mdutil.GetInt(md, "maxFails", "max_fails")
|
||||
failTimeout := mdutil.GetDuration(md, "failTimeout", "fail_timeout")
|
||||
if strategy == "" && maxFails <= 0 && failTimeout <= 0 {
|
||||
return nil
|
||||
}
|
||||
if strategy == "" {
|
||||
strategy = "round"
|
||||
}
|
||||
if maxFails <= 0 {
|
||||
maxFails = 1
|
||||
}
|
||||
if failTimeout <= 0 {
|
||||
failTimeout = 30 * time.Second
|
||||
}
|
||||
|
||||
delete(m, "strategy")
|
||||
delete(m, "maxFails")
|
||||
delete(m, "max_fails")
|
||||
delete(m, "failTimeout")
|
||||
delete(m, "fail_timeout")
|
||||
|
||||
return &config.SelectorConfig{
|
||||
Strategy: strategy,
|
||||
MaxFails: maxFails,
|
||||
FailTimeout: failTimeout,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,612 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
v = viper.GetViper()
|
||||
)
|
||||
|
||||
func init() {
|
||||
v.SetConfigName("gost")
|
||||
v.AddConfigPath("/etc/gost/")
|
||||
v.AddConfigPath("$HOME/.gost/")
|
||||
v.AddConfigPath(".")
|
||||
}
|
||||
|
||||
var (
|
||||
global = &Config{}
|
||||
globalMux sync.RWMutex
|
||||
)
|
||||
|
||||
func Global() *Config {
|
||||
globalMux.RLock()
|
||||
defer globalMux.RUnlock()
|
||||
|
||||
cfg := &Config{}
|
||||
*cfg = *global
|
||||
return cfg
|
||||
}
|
||||
|
||||
func Set(c *Config) {
|
||||
globalMux.Lock()
|
||||
defer globalMux.Unlock()
|
||||
|
||||
global = c
|
||||
}
|
||||
|
||||
func OnUpdate(f func(c *Config) error) error {
|
||||
globalMux.Lock()
|
||||
defer globalMux.Unlock()
|
||||
|
||||
return f(global)
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Output string `yaml:",omitempty" json:"output,omitempty"`
|
||||
Level string `yaml:",omitempty" json:"level,omitempty"`
|
||||
Format string `yaml:",omitempty" json:"format,omitempty"`
|
||||
Rotation *LogRotationConfig `yaml:",omitempty" json:"rotation,omitempty"`
|
||||
}
|
||||
|
||||
type LogRotationConfig struct {
|
||||
// MaxSize is the maximum size in megabytes of the log file before it gets
|
||||
// rotated. It defaults to 100 megabytes.
|
||||
MaxSize int `yaml:"maxSize,omitempty" json:"maxSize,omitempty"`
|
||||
// MaxAge is the maximum number of days to retain old log files based on the
|
||||
// timestamp encoded in their filename. Note that a day is defined as 24
|
||||
// hours and may not exactly correspond to calendar days due to daylight
|
||||
// savings, leap seconds, etc. The default is not to remove old log files
|
||||
// based on age.
|
||||
MaxAge int `yaml:"maxAge,omitempty" json:"maxAge,omitempty"`
|
||||
// MaxBackups is the maximum number of old log files to retain. The default
|
||||
// is to retain all old log files (though MaxAge may still cause them to get
|
||||
// deleted.)
|
||||
MaxBackups int `yaml:"maxBackups,omitempty" json:"maxBackups,omitempty"`
|
||||
// LocalTime determines if the time used for formatting the timestamps in
|
||||
// backup files is the computer's local time. The default is to use UTC
|
||||
// time.
|
||||
LocalTime bool `yaml:"localTime,omitempty" json:"localTime,omitempty"`
|
||||
// Compress determines if the rotated log files should be compressed
|
||||
// using gzip. The default is not to perform compression.
|
||||
Compress bool `yaml:"compress,omitempty" json:"compress,omitempty"`
|
||||
}
|
||||
|
||||
type LoggerConfig struct {
|
||||
Name string `json:"name"`
|
||||
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||
}
|
||||
|
||||
type ProfilingConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
|
||||
type APIConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
PathPrefix string `yaml:"pathPrefix,omitempty" json:"pathPrefix,omitempty"`
|
||||
AccessLog bool `yaml:"accesslog,omitempty" json:"accesslog,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
||||
}
|
||||
|
||||
type MetricsConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
||||
}
|
||||
|
||||
type TLSConfig struct {
|
||||
CertFile string `yaml:"certFile,omitempty" json:"certFile,omitempty"`
|
||||
KeyFile string `yaml:"keyFile,omitempty" json:"keyFile,omitempty"`
|
||||
CAFile string `yaml:"caFile,omitempty" json:"caFile,omitempty"`
|
||||
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
|
||||
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
|
||||
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
|
||||
|
||||
// for auto-generated default certificate.
|
||||
Validity time.Duration `yaml:",omitempty" json:"validity,omitempty"`
|
||||
CommonName string `yaml:"commonName,omitempty" json:"commonName,omitempty"`
|
||||
Organization string `yaml:",omitempty" json:"organization,omitempty"`
|
||||
}
|
||||
|
||||
type TLSOptions struct {
|
||||
MinVersion string `yaml:"minVersion,omitempty" json:"minVersion,omitempty"`
|
||||
MaxVersion string `yaml:"maxVersion,omitempty" json:"maxVersion,omitempty"`
|
||||
CipherSuites []string `yaml:"cipherSuites,omitempty" json:"cipherSuites,omitempty"`
|
||||
ALPN []string `yaml:"alpn,omitempty" json:"alpn,omitempty"`
|
||||
}
|
||||
|
||||
type PluginConfig struct {
|
||||
Type string `json:"type"`
|
||||
Addr string `json:"addr"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
||||
Token string `yaml:",omitempty" json:"token,omitempty"`
|
||||
}
|
||||
|
||||
type AutherConfig struct {
|
||||
Name string `json:"name"`
|
||||
Auths []*AuthConfig `yaml:",omitempty" json:"auths,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
Username string `json:"username"`
|
||||
Password string `yaml:",omitempty" json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type SelectorConfig struct {
|
||||
Strategy string `json:"strategy"`
|
||||
MaxFails int `yaml:"maxFails" json:"maxFails"`
|
||||
FailTimeout time.Duration `yaml:"failTimeout" json:"failTimeout"`
|
||||
}
|
||||
|
||||
type AdmissionConfig struct {
|
||||
Name string `json:"name"`
|
||||
// Deprecated: use whitelist instead
|
||||
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
|
||||
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
|
||||
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type BypassConfig struct {
|
||||
Name string `json:"name"`
|
||||
// Deprecated: use whitelist instead
|
||||
Reverse bool `yaml:",omitempty" json:"reverse,omitempty"`
|
||||
Whitelist bool `yaml:",omitempty" json:"whitelist,omitempty"`
|
||||
Matchers []string `yaml:",omitempty" json:"matchers,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type FileLoader struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type RedisLoader struct {
|
||||
Addr string `json:"addr"`
|
||||
DB int `yaml:",omitempty" json:"db,omitempty"`
|
||||
Username string `yaml:",omitempty" json:"username,omitempty"`
|
||||
Password string `yaml:",omitempty" json:"password,omitempty"`
|
||||
Key string `yaml:",omitempty" json:"key,omitempty"`
|
||||
Type string `yaml:",omitempty" json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPLoader struct {
|
||||
URL string `yaml:"url" json:"url"`
|
||||
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
type NameserverConfig struct {
|
||||
Addr string `json:"addr"`
|
||||
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
||||
Prefer string `yaml:",omitempty" json:"prefer,omitempty"`
|
||||
ClientIP string `yaml:"clientIP,omitempty" json:"clientIP,omitempty"`
|
||||
Hostname string `yaml:",omitempty" json:"hostname,omitempty"`
|
||||
TTL time.Duration `yaml:",omitempty" json:"ttl,omitempty"`
|
||||
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
||||
Async bool `yaml:",omitempty" json:"async,omitempty"`
|
||||
Only string `yaml:",omitempty" json:"only,omitempty"`
|
||||
}
|
||||
|
||||
type ResolverConfig struct {
|
||||
Name string `json:"name"`
|
||||
Nameservers []*NameserverConfig `yaml:",omitempty" json:"nameservers,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type HostMappingConfig struct {
|
||||
IP string `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
Aliases []string `yaml:",omitempty" json:"aliases,omitempty"`
|
||||
}
|
||||
|
||||
type HostsConfig struct {
|
||||
Name string `json:"name"`
|
||||
Mappings []*HostMappingConfig `yaml:",omitempty" json:"mappings,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type IngressRuleConfig struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
}
|
||||
|
||||
type IngressConfig struct {
|
||||
Name string `json:"name"`
|
||||
Rules []*IngressRuleConfig `yaml:",omitempty" json:"rules,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type SDConfig struct {
|
||||
Name string `json:"name"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type RouterRouteConfig struct {
|
||||
// Deprecated: use dst instead
|
||||
Net string `yaml:",omitempty" json:"net,omitempty"`
|
||||
Dst string `yaml:",omitempty" json:"dst,omitempty"`
|
||||
Gateway string `json:"gateway"`
|
||||
}
|
||||
|
||||
type RouterConfig struct {
|
||||
Name string `json:"name"`
|
||||
Routes []*RouterRouteConfig `yaml:",omitempty" json:"routes,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type RecorderConfig struct {
|
||||
Name string `json:"name"`
|
||||
File *FileRecorder `yaml:",omitempty" json:"file,omitempty"`
|
||||
TCP *TCPRecorder `yaml:"tcp,omitempty" json:"tcp,omitempty"`
|
||||
HTTP *HTTPRecorder `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Redis *RedisRecorder `yaml:",omitempty" json:"redis,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type FileRecorder struct {
|
||||
Path string `json:"path"`
|
||||
Sep string `yaml:",omitempty" json:"sep,omitempty"`
|
||||
Rotation *LogRotationConfig `yaml:",omitempty" json:"rotation,omitempty"`
|
||||
}
|
||||
|
||||
type TCPRecorder struct {
|
||||
Addr string `json:"addr"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
}
|
||||
|
||||
type HTTPRecorder struct {
|
||||
URL string `yaml:"url" json:"url"`
|
||||
Timeout time.Duration `yaml:",omitempty" json:"timeout,omitempty"`
|
||||
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
||||
}
|
||||
|
||||
type RedisRecorder struct {
|
||||
Addr string `json:"addr"`
|
||||
DB int `yaml:",omitempty" json:"db,omitempty"`
|
||||
Username string `yaml:",omitempty" json:"username,omitempty"`
|
||||
Password string `yaml:",omitempty" json:"password,omitempty"`
|
||||
Key string `yaml:",omitempty" json:"key,omitempty"`
|
||||
Type string `yaml:",omitempty" json:"type,omitempty"`
|
||||
}
|
||||
|
||||
type RecorderObject struct {
|
||||
Name string `json:"name"`
|
||||
Record string `json:"record"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type LimiterConfig struct {
|
||||
Name string `json:"name"`
|
||||
Limits []string `yaml:",omitempty" json:"limits,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type ObserverConfig struct {
|
||||
Name string `json:"name"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
type ListenerConfig struct {
|
||||
Type string `json:"type"`
|
||||
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
||||
ChainGroup *ChainGroupConfig `yaml:"chainGroup,omitempty" json:"chainGroup,omitempty"`
|
||||
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
||||
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type HandlerConfig struct {
|
||||
Type string `json:"type"`
|
||||
Retries int `yaml:",omitempty" json:"retries,omitempty"`
|
||||
Chain string `yaml:",omitempty" json:"chain,omitempty"`
|
||||
ChainGroup *ChainGroupConfig `yaml:"chainGroup,omitempty" json:"chainGroup,omitempty"`
|
||||
Auther string `yaml:",omitempty" json:"auther,omitempty"`
|
||||
Authers []string `yaml:",omitempty" json:"authers,omitempty"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
||||
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ForwarderConfig struct {
|
||||
// Deprecated: use hop instead
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
// the referenced hop name
|
||||
Hop string `yaml:",omitempty" json:"hop,omitempty"`
|
||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||
Nodes []*ForwardNodeConfig `json:"nodes"`
|
||||
}
|
||||
|
||||
type ForwardNodeConfig struct {
|
||||
Name string `yaml:",omitempty" json:"name,omitempty"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
// Deprecated: use matcher instead
|
||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||
// Deprecated: use matcher instead
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
// Deprecated: use matcher instead
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
// Deprecated: use matcher instead
|
||||
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
||||
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
||||
// Deprecated: use http.auth instead
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPURLRewriteConfig struct {
|
||||
Match string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
type HTTPBodyRewriteConfig struct {
|
||||
// filter by MIME types
|
||||
Type string
|
||||
Match string
|
||||
Replacement string
|
||||
}
|
||||
|
||||
type NodeFilterConfig struct {
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
Protocol string `yaml:",omitempty" json:"protocol,omitempty"`
|
||||
Path string `yaml:",omitempty" json:"path,omitempty"`
|
||||
}
|
||||
|
||||
type NodeMatcherConfig struct {
|
||||
Rule string `yaml:",omitempty" json:"rule,omitempty"`
|
||||
Priority int `yaml:",omitempty" json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
type HTTPNodeConfig struct {
|
||||
// rewrite host header
|
||||
Host string `yaml:",omitempty" json:"host,omitempty"`
|
||||
// Deprecated: use requestHeader instead
|
||||
Header map[string]string `yaml:",omitempty" json:"header,omitempty"`
|
||||
// additional request header
|
||||
RequestHeader map[string]string `yaml:"requestHeader,omitempty" json:"requestHeader,omitempty"`
|
||||
// additional response header
|
||||
ResponseHeader map[string]string `yaml:"responseHeader,omitempty" json:"responseHeader,omitempty"`
|
||||
// Deprecated: use rewriteURL instead
|
||||
Rewrite []HTTPURLRewriteConfig `yaml:",omitempty" json:"rewrite,omitempty"`
|
||||
// rewrite URL
|
||||
RewriteURL []HTTPURLRewriteConfig `yaml:"rewriteURL,omitempty" json:"rewriteURL,omitempty"`
|
||||
// rewrite response body
|
||||
RewriteBody []HTTPBodyRewriteConfig `yaml:"rewriteBody,omitempty" json:"rewriteBody,omitempty"`
|
||||
// HTTP basic auth
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
type TLSNodeConfig struct {
|
||||
ServerName string `yaml:"serverName,omitempty" json:"serverName,omitempty"`
|
||||
Secure bool `yaml:",omitempty" json:"secure,omitempty"`
|
||||
Options *TLSOptions `yaml:",omitempty" json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type DialerConfig struct {
|
||||
Type string `json:"type"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ConnectorConfig struct {
|
||||
Type string `json:"type"`
|
||||
Auth *AuthConfig `yaml:",omitempty" json:"auth,omitempty"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type SockOptsConfig struct {
|
||||
Mark int `yaml:",omitempty" json:"mark,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceConfig struct {
|
||||
Name string `json:"name"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
// Deprecated: use metadata.interface instead
|
||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||
// Deprecated: use metadata.so_mark instead
|
||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||
Admission string `yaml:",omitempty" json:"admission,omitempty"`
|
||||
Admissions []string `yaml:",omitempty" json:"admissions,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Limiter string `yaml:",omitempty" json:"limiter,omitempty"`
|
||||
CLimiter string `yaml:"climiter,omitempty" json:"climiter,omitempty"`
|
||||
RLimiter string `yaml:"rlimiter,omitempty" json:"rlimiter,omitempty"`
|
||||
Logger string `yaml:",omitempty" json:"logger,omitempty"`
|
||||
Loggers []string `yaml:",omitempty" json:"loggers,omitempty"`
|
||||
Observer string `yaml:",omitempty" json:"observer,omitempty"`
|
||||
Recorders []*RecorderObject `yaml:",omitempty" json:"recorders,omitempty"`
|
||||
Handler *HandlerConfig `yaml:",omitempty" json:"handler,omitempty"`
|
||||
Listener *ListenerConfig `yaml:",omitempty" json:"listener,omitempty"`
|
||||
Forwarder *ForwarderConfig `yaml:",omitempty" json:"forwarder,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
// service status, read-only
|
||||
Status *ServiceStatus `yaml:",omitempty" json:"status,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
CreateTime int64 `yaml:"createTime" json:"createTime"`
|
||||
State string `yaml:"state" json:"state"`
|
||||
Events []ServiceEvent `yaml:",omitempty" json:"events,omitempty"`
|
||||
Stats *ServiceStats `yaml:",omitempty" json:"stats,omitempty"`
|
||||
}
|
||||
|
||||
type ServiceEvent struct {
|
||||
Time int64 `yaml:"time" json:"time"`
|
||||
Msg string `yaml:"msg" json:"msg"`
|
||||
}
|
||||
|
||||
type ServiceStats struct {
|
||||
TotalConns uint64 `yaml:"totalConns" json:"totalConns"`
|
||||
CurrentConns uint64 `yaml:"currentConns" json:"currentConns"`
|
||||
TotalErrs uint64 `yaml:"totalErrs" json:"totalErrs"`
|
||||
InputBytes uint64 `yaml:"inputBytes" json:"inputBytes"`
|
||||
OutputBytes uint64 `yaml:"outputBytes" json:"outputBytes"`
|
||||
}
|
||||
|
||||
type ChainConfig struct {
|
||||
Name string `json:"name"`
|
||||
Hops []*HopConfig `json:"hops"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type ChainGroupConfig struct {
|
||||
Chains []string `yaml:",omitempty" json:"chains,omitempty"`
|
||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||
}
|
||||
|
||||
type HopConfig struct {
|
||||
Name string `json:"name"`
|
||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||
Selector *SelectorConfig `yaml:",omitempty" json:"selector,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Nodes []*NodeConfig `yaml:",omitempty" json:"nodes,omitempty"`
|
||||
Reload time.Duration `yaml:",omitempty" json:"reload,omitempty"`
|
||||
File *FileLoader `yaml:",omitempty" json:"file,omitempty"`
|
||||
Redis *RedisLoader `yaml:",omitempty" json:"redis,omitempty"`
|
||||
HTTP *HTTPLoader `yaml:"http,omitempty" json:"http,omitempty"`
|
||||
Plugin *PluginConfig `yaml:",omitempty" json:"plugin,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type NodeConfig struct {
|
||||
Name string `json:"name"`
|
||||
Addr string `yaml:",omitempty" json:"addr,omitempty"`
|
||||
Network string `yaml:",omitempty" json:"network,omitempty"`
|
||||
Bypass string `yaml:",omitempty" json:"bypass,omitempty"`
|
||||
Bypasses []string `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolver string `yaml:",omitempty" json:"resolver,omitempty"`
|
||||
Hosts string `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Connector *ConnectorConfig `yaml:",omitempty" json:"connector,omitempty"`
|
||||
Dialer *DialerConfig `yaml:",omitempty" json:"dialer,omitempty"`
|
||||
Interface string `yaml:",omitempty" json:"interface,omitempty"`
|
||||
Netns string `yaml:",omitempty" json:"netns,omitempty"`
|
||||
SockOpts *SockOptsConfig `yaml:"sockopts,omitempty" json:"sockopts,omitempty"`
|
||||
// Deprecated: use matcher instead
|
||||
Filter *NodeFilterConfig `yaml:",omitempty" json:"filter,omitempty"`
|
||||
Matcher *NodeMatcherConfig `yaml:",omitempty" json:"matcher,omitempty"`
|
||||
HTTP *HTTPNodeConfig `yaml:",omitempty" json:"http,omitempty"`
|
||||
TLS *TLSNodeConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Metadata map[string]any `yaml:",omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Services []*ServiceConfig `json:"services"`
|
||||
Chains []*ChainConfig `yaml:",omitempty" json:"chains,omitempty"`
|
||||
Hops []*HopConfig `yaml:",omitempty" json:"hops,omitempty"`
|
||||
Authers []*AutherConfig `yaml:",omitempty" json:"authers,omitempty"`
|
||||
Admissions []*AdmissionConfig `yaml:",omitempty" json:"admissions,omitempty"`
|
||||
Bypasses []*BypassConfig `yaml:",omitempty" json:"bypasses,omitempty"`
|
||||
Resolvers []*ResolverConfig `yaml:",omitempty" json:"resolvers,omitempty"`
|
||||
Hosts []*HostsConfig `yaml:",omitempty" json:"hosts,omitempty"`
|
||||
Ingresses []*IngressConfig `yaml:",omitempty" json:"ingresses,omitempty"`
|
||||
Routers []*RouterConfig `yaml:",omitempty" json:"routers,omitempty"`
|
||||
SDs []*SDConfig `yaml:"sds,omitempty" json:"sds,omitempty"`
|
||||
Recorders []*RecorderConfig `yaml:",omitempty" json:"recorders,omitempty"`
|
||||
Limiters []*LimiterConfig `yaml:",omitempty" json:"limiters,omitempty"`
|
||||
CLimiters []*LimiterConfig `yaml:"climiters,omitempty" json:"climiters,omitempty"`
|
||||
RLimiters []*LimiterConfig `yaml:"rlimiters,omitempty" json:"rlimiters,omitempty"`
|
||||
Observers []*ObserverConfig `yaml:",omitempty" json:"observers,omitempty"`
|
||||
Loggers []*LoggerConfig `yaml:",omitempty" json:"loggers,omitempty"`
|
||||
TLS *TLSConfig `yaml:",omitempty" json:"tls,omitempty"`
|
||||
Log *LogConfig `yaml:",omitempty" json:"log,omitempty"`
|
||||
Profiling *ProfilingConfig `yaml:",omitempty" json:"profiling,omitempty"`
|
||||
API *APIConfig `yaml:",omitempty" json:"api,omitempty"`
|
||||
Metrics *MetricsConfig `yaml:",omitempty" json:"metrics,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Config) Load() error {
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Unmarshal(c)
|
||||
}
|
||||
|
||||
func (c *Config) Read(r io.Reader) error {
|
||||
if err := v.ReadConfig(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Unmarshal(c)
|
||||
}
|
||||
|
||||
func (c *Config) ReadFile(file string) error {
|
||||
v.SetConfigFile(file)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.Unmarshal(c)
|
||||
}
|
||||
|
||||
func (c *Config) Write(w io.Writer, format string) error {
|
||||
switch format {
|
||||
case "json":
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.Encode(c)
|
||||
return nil
|
||||
case "yaml":
|
||||
fallthrough
|
||||
default:
|
||||
enc := yaml.NewEncoder(w)
|
||||
defer enc.Close()
|
||||
enc.SetIndent(2)
|
||||
|
||||
return enc.Encode(c)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/parsing"
|
||||
admission_parser "github.com/go-gost/x/config/parsing/admission"
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
chain_parser "github.com/go-gost/x/config/parsing/chain"
|
||||
hop_parser "github.com/go-gost/x/config/parsing/hop"
|
||||
hosts_parser "github.com/go-gost/x/config/parsing/hosts"
|
||||
ingress_parser "github.com/go-gost/x/config/parsing/ingress"
|
||||
limiter_parser "github.com/go-gost/x/config/parsing/limiter"
|
||||
logger_parser "github.com/go-gost/x/config/parsing/logger"
|
||||
observer_parser "github.com/go-gost/x/config/parsing/observer"
|
||||
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
|
||||
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
|
||||
router_parser "github.com/go-gost/x/config/parsing/router"
|
||||
sd_parser "github.com/go-gost/x/config/parsing/sd"
|
||||
service_parser "github.com/go-gost/x/config/parsing/service"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLoader *loader = &loader{}
|
||||
)
|
||||
|
||||
func Load(cfg *config.Config) error {
|
||||
return defaultLoader.Load(cfg)
|
||||
}
|
||||
|
||||
type loader struct{}
|
||||
|
||||
func (l *loader) Load(cfg *config.Config) error {
|
||||
logCfg := cfg.Log
|
||||
if logCfg == nil {
|
||||
logCfg = &config.LogConfig{}
|
||||
}
|
||||
logger.SetDefault(logger_parser.ParseLogger(&config.LoggerConfig{Log: logCfg}))
|
||||
|
||||
tlsCfg, err := parsing.BuildDefaultTLSConfig(cfg.TLS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsing.SetDefaultTLSConfig(tlsCfg)
|
||||
|
||||
if err := register(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func register(cfg *config.Config) error {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for name := range registry.LoggerRegistry().GetAll() {
|
||||
registry.LoggerRegistry().Unregister(name)
|
||||
}
|
||||
for _, loggerCfg := range cfg.Loggers {
|
||||
if err := registry.LoggerRegistry().Register(loggerCfg.Name, logger_parser.ParseLogger(loggerCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.AutherRegistry().GetAll() {
|
||||
registry.AutherRegistry().Unregister(name)
|
||||
}
|
||||
for _, autherCfg := range cfg.Authers {
|
||||
if err := registry.AutherRegistry().Register(autherCfg.Name, auth_parser.ParseAuther(autherCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.AdmissionRegistry().GetAll() {
|
||||
registry.AdmissionRegistry().Unregister(name)
|
||||
}
|
||||
for _, admissionCfg := range cfg.Admissions {
|
||||
if err := registry.AdmissionRegistry().Register(admissionCfg.Name, admission_parser.ParseAdmission(admissionCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.BypassRegistry().GetAll() {
|
||||
registry.BypassRegistry().Unregister(name)
|
||||
}
|
||||
for _, bypassCfg := range cfg.Bypasses {
|
||||
if err := registry.BypassRegistry().Register(bypassCfg.Name, bypass_parser.ParseBypass(bypassCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.ResolverRegistry().GetAll() {
|
||||
registry.ResolverRegistry().Unregister(name)
|
||||
}
|
||||
for _, resolverCfg := range cfg.Resolvers {
|
||||
r, err := resolver_parser.ParseResolver(resolverCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := registry.ResolverRegistry().Register(resolverCfg.Name, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.HostsRegistry().GetAll() {
|
||||
registry.HostsRegistry().Unregister(name)
|
||||
}
|
||||
for _, hostsCfg := range cfg.Hosts {
|
||||
if err := registry.HostsRegistry().Register(hostsCfg.Name, hosts_parser.ParseHostMapper(hostsCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.IngressRegistry().GetAll() {
|
||||
registry.IngressRegistry().Unregister(name)
|
||||
}
|
||||
for _, ingressCfg := range cfg.Ingresses {
|
||||
if err := registry.IngressRegistry().Register(ingressCfg.Name, ingress_parser.ParseIngress(ingressCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.RouterRegistry().GetAll() {
|
||||
registry.RouterRegistry().Unregister(name)
|
||||
}
|
||||
for _, routerCfg := range cfg.Routers {
|
||||
if err := registry.RouterRegistry().Register(routerCfg.Name, router_parser.ParseRouter(routerCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.SDRegistry().GetAll() {
|
||||
registry.SDRegistry().Unregister(name)
|
||||
}
|
||||
for _, sdCfg := range cfg.SDs {
|
||||
if err := registry.SDRegistry().Register(sdCfg.Name, sd_parser.ParseSD(sdCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.ObserverRegistry().GetAll() {
|
||||
registry.ObserverRegistry().Unregister(name)
|
||||
}
|
||||
for _, observerCfg := range cfg.Observers {
|
||||
if err := registry.ObserverRegistry().Register(observerCfg.Name, observer_parser.ParseObserver(observerCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.RecorderRegistry().GetAll() {
|
||||
registry.RecorderRegistry().Unregister(name)
|
||||
}
|
||||
for _, recorderCfg := range cfg.Recorders {
|
||||
if err := registry.RecorderRegistry().Register(recorderCfg.Name, recorder_parser.ParseRecorder(recorderCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.TrafficLimiterRegistry().GetAll() {
|
||||
registry.TrafficLimiterRegistry().Unregister(name)
|
||||
}
|
||||
for _, limiterCfg := range cfg.Limiters {
|
||||
if err := registry.TrafficLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseTrafficLimiter(limiterCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.ConnLimiterRegistry().GetAll() {
|
||||
registry.ConnLimiterRegistry().Unregister(name)
|
||||
}
|
||||
for _, limiterCfg := range cfg.CLimiters {
|
||||
if err := registry.ConnLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseConnLimiter(limiterCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.RateLimiterRegistry().GetAll() {
|
||||
registry.RateLimiterRegistry().Unregister(name)
|
||||
}
|
||||
for _, limiterCfg := range cfg.RLimiters {
|
||||
if err := registry.RateLimiterRegistry().Register(limiterCfg.Name, limiter_parser.ParseRateLimiter(limiterCfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.HopRegistry().GetAll() {
|
||||
registry.HopRegistry().Unregister(name)
|
||||
}
|
||||
for _, hopCfg := range cfg.Hops {
|
||||
hop, err := hop_parser.ParseHop(hopCfg, logger.Default())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := registry.HopRegistry().Register(hopCfg.Name, hop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.ChainRegistry().GetAll() {
|
||||
registry.ChainRegistry().Unregister(name)
|
||||
}
|
||||
for _, chainCfg := range cfg.Chains {
|
||||
c, err := chain_parser.ParseChain(chainCfg, logger.Default())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := registry.ChainRegistry().Register(chainCfg.Name, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for name := range registry.ServiceRegistry().GetAll() {
|
||||
registry.ServiceRegistry().Unregister(name)
|
||||
}
|
||||
for _, svcCfg := range cfg.Services {
|
||||
svc, err := service_parser.ParseService(svcCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svc != nil {
|
||||
if err := registry.ServiceRegistry().Register(svcCfg.Name, svc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package admission
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/admission"
|
||||
"github.com/go-gost/core/logger"
|
||||
xadmission "github.com/go-gost/x/admission"
|
||||
admission_plugin "github.com/go-gost/x/admission/plugin"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func ParseAdmission(cfg *config.AdmissionConfig) admission.Admission {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return admission_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return admission_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
opts := []xadmission.Option{
|
||||
xadmission.MatchersOption(cfg.Matchers),
|
||||
xadmission.WhitelistOption(cfg.Reverse || cfg.Whitelist),
|
||||
xadmission.ReloadPeriodOption(cfg.Reload),
|
||||
xadmission.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "admission",
|
||||
"admission": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xadmission.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
opts = append(opts, xadmission.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xadmission.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
|
||||
return xadmission.NewAdmission(opts...)
|
||||
}
|
||||
|
||||
func List(name string, names ...string) []admission.Admission {
|
||||
var admissions []admission.Admission
|
||||
if adm := registry.AdmissionRegistry().Get(name); adm != nil {
|
||||
admissions = append(admissions, adm)
|
||||
}
|
||||
for _, s := range names {
|
||||
if adm := registry.AdmissionRegistry().Get(s); adm != nil {
|
||||
admissions = append(admissions, adm)
|
||||
}
|
||||
}
|
||||
|
||||
return admissions
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/logger"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
auth_plugin "github.com/go-gost/x/auth/plugin"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func ParseAuther(cfg *config.AutherConfig) auth.Authenticator {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch cfg.Plugin.Type {
|
||||
case "http":
|
||||
return auth_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return auth_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
m := make(map[string]string)
|
||||
|
||||
for _, user := range cfg.Auths {
|
||||
if user.Username == "" {
|
||||
continue
|
||||
}
|
||||
m[user.Username] = user.Password
|
||||
}
|
||||
|
||||
opts := []xauth.Option{
|
||||
xauth.AuthsOption(m),
|
||||
xauth.ReloadPeriodOption(cfg.Reload),
|
||||
xauth.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "auther",
|
||||
"auther": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xauth.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
opts = append(opts, xauth.RedisLoaderOption(loader.RedisHashLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xauth.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
return xauth.NewAuthenticator(opts...)
|
||||
}
|
||||
|
||||
func ParseAutherFromAuth(au *config.AuthConfig) auth.Authenticator {
|
||||
if au == nil || au.Username == "" {
|
||||
return nil
|
||||
}
|
||||
return xauth.NewAuthenticator(
|
||||
xauth.AuthsOption(
|
||||
map[string]string{
|
||||
au.Username: au.Password,
|
||||
},
|
||||
),
|
||||
xauth.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "auther",
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
func Info(cfg *config.AuthConfig) *url.Userinfo {
|
||||
if cfg == nil || cfg.Username == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Password == "" {
|
||||
return url.User(cfg.Username)
|
||||
}
|
||||
return url.UserPassword(cfg.Username, cfg.Password)
|
||||
}
|
||||
|
||||
func List(name string, names ...string) []auth.Authenticator {
|
||||
var authers []auth.Authenticator
|
||||
if auther := registry.AutherRegistry().Get(name); auther != nil {
|
||||
authers = append(authers, auther)
|
||||
}
|
||||
for _, s := range names {
|
||||
if auther := registry.AutherRegistry().Get(s); auther != nil {
|
||||
authers = append(authers, auther)
|
||||
}
|
||||
}
|
||||
return authers
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package bypass
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/bypass"
|
||||
"github.com/go-gost/core/logger"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
bypass_plugin "github.com/go-gost/x/bypass/plugin"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func ParseBypass(cfg *config.BypassConfig) bypass.Bypass {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return bypass_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return bypass_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
opts := []xbypass.Option{
|
||||
xbypass.MatchersOption(cfg.Matchers),
|
||||
xbypass.WhitelistOption(cfg.Reverse || cfg.Whitelist),
|
||||
xbypass.ReloadPeriodOption(cfg.Reload),
|
||||
xbypass.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "bypass",
|
||||
"bypass": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xbypass.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
opts = append(opts, xbypass.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xbypass.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
|
||||
return xbypass.NewBypass(opts...)
|
||||
}
|
||||
|
||||
func List(name string, names ...string) []bypass.Bypass {
|
||||
var bypasses []bypass.Bypass
|
||||
if bp := registry.BypassRegistry().Get(name); bp != nil {
|
||||
bypasses = append(bypasses, bp)
|
||||
}
|
||||
for _, s := range names {
|
||||
if bp := registry.BypassRegistry().Get(s); bp != nil {
|
||||
bypasses = append(bypasses, bp)
|
||||
}
|
||||
}
|
||||
return bypasses
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/hop"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/metadata"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
hop_parser "github.com/go-gost/x/config/parsing/hop"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func ParseChain(cfg *config.ChainConfig, log logger.Logger) (chain.Chainer, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
chainLogger := log.WithFields(map[string]any{
|
||||
"kind": "chain",
|
||||
"chain": cfg.Name,
|
||||
})
|
||||
|
||||
var md metadata.Metadata
|
||||
if cfg.Metadata != nil {
|
||||
md = mdx.NewMetadata(cfg.Metadata)
|
||||
}
|
||||
|
||||
c := xchain.NewChain(cfg.Name,
|
||||
xchain.MetadataChainOption(md),
|
||||
xchain.LoggerChainOption(chainLogger),
|
||||
)
|
||||
|
||||
for _, ch := range cfg.Hops {
|
||||
var hop hop.Hop
|
||||
var err error
|
||||
|
||||
if ch.Nodes != nil || ch.Plugin != nil {
|
||||
if hop, err = hop_parser.ParseHop(ch, log); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
hop = registry.HopRegistry().Get(ch.Name)
|
||||
}
|
||||
if hop != nil {
|
||||
c.AddHop(hop)
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package hop
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/hop"
|
||||
"github.com/go-gost/core/logger"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/parsing"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
node_parser "github.com/go-gost/x/config/parsing/node"
|
||||
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
||||
xhop "github.com/go-gost/x/hop"
|
||||
hop_plugin "github.com/go-gost/x/hop/plugin"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
func ParseHop(cfg *config.HopConfig, log logger.Logger) (hop.Hop, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case plugin.HTTP:
|
||||
return hop_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
), nil
|
||||
default:
|
||||
return hop_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
), nil
|
||||
}
|
||||
}
|
||||
|
||||
ifce := cfg.Interface
|
||||
var netns string
|
||||
if cfg.Metadata != nil {
|
||||
md := metadata.NewMetadata(cfg.Metadata)
|
||||
if v := mdutil.GetString(md, parsing.MDKeyInterface); v != "" {
|
||||
ifce = v
|
||||
}
|
||||
netns = mdutil.GetString(md, "netns")
|
||||
}
|
||||
|
||||
var nodes []*chain.Node
|
||||
for _, v := range cfg.Nodes {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Resolver == "" {
|
||||
v.Resolver = cfg.Resolver
|
||||
}
|
||||
if v.Hosts == "" {
|
||||
v.Hosts = cfg.Hosts
|
||||
}
|
||||
if v.Interface == "" {
|
||||
v.Interface = ifce
|
||||
}
|
||||
if v.Netns == "" {
|
||||
v.Netns = netns
|
||||
}
|
||||
|
||||
if v.SockOpts == nil {
|
||||
v.SockOpts = cfg.SockOpts
|
||||
}
|
||||
|
||||
if v.Connector == nil {
|
||||
v.Connector = &config.ConnectorConfig{}
|
||||
}
|
||||
if strings.TrimSpace(v.Connector.Type) == "" {
|
||||
v.Connector.Type = "http"
|
||||
}
|
||||
|
||||
if v.Dialer == nil {
|
||||
v.Dialer = &config.DialerConfig{}
|
||||
}
|
||||
if strings.TrimSpace(v.Dialer.Type) == "" {
|
||||
v.Dialer.Type = "tcp"
|
||||
}
|
||||
|
||||
node, err := node_parser.ParseNode(cfg.Name, v, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node != nil {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
sel := selector_parser.ParseNodeSelector(cfg.Selector)
|
||||
if sel == nil {
|
||||
sel = selector_parser.DefaultNodeSelector()
|
||||
}
|
||||
|
||||
opts := []xhop.Option{
|
||||
xhop.NameOption(cfg.Name),
|
||||
xhop.NodeOption(nodes...),
|
||||
xhop.SelectorOption(sel),
|
||||
xhop.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
xhop.ReloadPeriodOption(cfg.Reload),
|
||||
xhop.LoggerOption(log.WithFields(map[string]any{
|
||||
"kind": "hop",
|
||||
"hop": cfg.Name,
|
||||
})),
|
||||
}
|
||||
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xhop.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
opts = append(opts, xhop.RedisLoaderOption(loader.RedisStringLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xhop.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
return xhop.NewHop(opts...), nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package hosts
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/hosts"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
xhosts "github.com/go-gost/x/hosts"
|
||||
hosts_plugin "github.com/go-gost/x/hosts/plugin"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
func ParseHostMapper(cfg *config.HostsConfig) hosts.HostMapper {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return hosts_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return hosts_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var mappings []xhosts.Mapping
|
||||
for _, mapping := range cfg.Mappings {
|
||||
if mapping.IP == "" || mapping.Hostname == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(mapping.IP)
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
mappings = append(mappings, xhosts.Mapping{
|
||||
Hostname: mapping.Hostname,
|
||||
IP: ip,
|
||||
})
|
||||
}
|
||||
opts := []xhosts.Option{
|
||||
xhosts.MappingsOption(mappings),
|
||||
xhosts.ReloadPeriodOption(cfg.Reload),
|
||||
xhosts.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "hosts",
|
||||
"hosts": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xhosts.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // redis list
|
||||
opts = append(opts, xhosts.RedisLoaderOption(loader.RedisListLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis set
|
||||
opts = append(opts, xhosts.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xhosts.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
return xhosts.NewHostMapper(opts...)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package ingress
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/ingress"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
xingress "github.com/go-gost/x/ingress"
|
||||
ingress_plugin "github.com/go-gost/x/ingress/plugin"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
)
|
||||
|
||||
func ParseIngress(cfg *config.IngressConfig) ingress.Ingress {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return ingress_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return ingress_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var rules []*ingress.Rule
|
||||
for _, rule := range cfg.Rules {
|
||||
if rule.Hostname == "" || rule.Endpoint == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
rules = append(rules, &ingress.Rule{
|
||||
Hostname: rule.Hostname,
|
||||
Endpoint: rule.Endpoint,
|
||||
})
|
||||
}
|
||||
opts := []xingress.Option{
|
||||
xingress.RulesOption(rules),
|
||||
xingress.ReloadPeriodOption(cfg.Reload),
|
||||
xingress.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "ingress",
|
||||
"ingress": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xingress.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "set": // redis set
|
||||
opts = append(opts, xingress.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis hash
|
||||
opts = append(opts, xingress.RedisLoaderOption(loader.RedisHashLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xingress.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
return xingress.NewIngress(opts...)
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/limiter/conn"
|
||||
"github.com/go-gost/core/limiter/rate"
|
||||
"github.com/go-gost/core/limiter/traffic"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
xconn "github.com/go-gost/x/limiter/conn"
|
||||
xrate "github.com/go-gost/x/limiter/rate"
|
||||
xtraffic "github.com/go-gost/x/limiter/traffic"
|
||||
traffic_plugin "github.com/go-gost/x/limiter/traffic/plugin"
|
||||
)
|
||||
|
||||
func ParseTrafficLimiter(cfg *config.LimiterConfig) (lim traffic.TrafficLimiter) {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return traffic_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return traffic_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var opts []xtraffic.Option
|
||||
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xtraffic.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // redis list
|
||||
opts = append(opts, xtraffic.RedisLoaderOption(loader.RedisListLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis set
|
||||
opts = append(opts, xtraffic.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xtraffic.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
opts = append(opts,
|
||||
xtraffic.LimitsOption(cfg.Limits...),
|
||||
xtraffic.ReloadPeriodOption(cfg.Reload),
|
||||
xtraffic.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "limiter",
|
||||
"limiter": cfg.Name,
|
||||
})),
|
||||
)
|
||||
|
||||
return xtraffic.NewTrafficLimiter(opts...)
|
||||
}
|
||||
|
||||
func ParseConnLimiter(cfg *config.LimiterConfig) (lim conn.ConnLimiter) {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var opts []xconn.Option
|
||||
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xconn.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // redis list
|
||||
opts = append(opts, xconn.RedisLoaderOption(loader.RedisListLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis set
|
||||
opts = append(opts, xconn.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xconn.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
opts = append(opts,
|
||||
xconn.LimitsOption(cfg.Limits...),
|
||||
xconn.ReloadPeriodOption(cfg.Reload),
|
||||
xconn.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "limiter",
|
||||
"limiter": cfg.Name,
|
||||
})),
|
||||
)
|
||||
|
||||
return xconn.NewConnLimiter(opts...)
|
||||
}
|
||||
|
||||
func ParseRateLimiter(cfg *config.LimiterConfig) (lim rate.RateLimiter) {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var opts []xrate.Option
|
||||
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xrate.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // redis list
|
||||
opts = append(opts, xrate.RedisLoaderOption(loader.RedisListLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis set
|
||||
opts = append(opts, xrate.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xrate.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
opts = append(opts,
|
||||
xrate.LimitsOption(cfg.Limits...),
|
||||
xrate.ReloadPeriodOption(cfg.Reload),
|
||||
xrate.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "limiter",
|
||||
"limiter": cfg.Name,
|
||||
})),
|
||||
)
|
||||
|
||||
return xrate.NewRateLimiter(opts...)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
xlogger "github.com/go-gost/x/logger"
|
||||
"github.com/go-gost/x/registry"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
func ParseLogger(cfg *config.LoggerConfig) logger.Logger {
|
||||
if cfg == nil || cfg.Log == nil {
|
||||
return nil
|
||||
}
|
||||
opts := []xlogger.Option{
|
||||
xlogger.NameOption(cfg.Name),
|
||||
xlogger.FormatOption(logger.LogFormat(cfg.Log.Format)),
|
||||
xlogger.LevelOption(logger.LogLevel(cfg.Log.Level)),
|
||||
}
|
||||
|
||||
var out io.Writer = os.Stderr
|
||||
switch cfg.Log.Output {
|
||||
case "none", "null":
|
||||
return xlogger.Nop()
|
||||
case "stdout":
|
||||
out = os.Stdout
|
||||
case "stderr", "":
|
||||
out = os.Stderr
|
||||
default:
|
||||
if cfg.Log.Rotation != nil {
|
||||
out = &lumberjack.Logger{
|
||||
Filename: cfg.Log.Output,
|
||||
MaxSize: cfg.Log.Rotation.MaxSize,
|
||||
MaxAge: cfg.Log.Rotation.MaxAge,
|
||||
MaxBackups: cfg.Log.Rotation.MaxBackups,
|
||||
LocalTime: cfg.Log.Rotation.LocalTime,
|
||||
Compress: cfg.Log.Rotation.Compress,
|
||||
}
|
||||
} else {
|
||||
os.MkdirAll(filepath.Dir(cfg.Log.Output), 0755)
|
||||
f, err := os.OpenFile(cfg.Log.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
logger.Default().Warn(err)
|
||||
} else {
|
||||
out = f
|
||||
}
|
||||
}
|
||||
}
|
||||
opts = append(opts, xlogger.OutputOption(out))
|
||||
|
||||
return xlogger.NewLogger(opts...)
|
||||
}
|
||||
|
||||
func List(name string, names ...string) []logger.Logger {
|
||||
var loggers []logger.Logger
|
||||
if adm := registry.LoggerRegistry().Get(name); adm != nil {
|
||||
loggers = append(loggers, adm)
|
||||
}
|
||||
for _, s := range names {
|
||||
if lg := registry.LoggerRegistry().Get(s); lg != nil {
|
||||
loggers = append(loggers, lg)
|
||||
}
|
||||
}
|
||||
|
||||
return loggers
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/dialer"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/metadata"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/parsing"
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
"github.com/go-gost/x/registry"
|
||||
"github.com/go-gost/x/routing"
|
||||
)
|
||||
|
||||
func ParseNode(hop string, cfg *config.NodeConfig, log logger.Logger) (*chain.Node, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cfg.Connector == nil {
|
||||
cfg.Connector = &config.ConnectorConfig{
|
||||
Type: "http",
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.Dialer == nil {
|
||||
cfg.Dialer = &config.DialerConfig{
|
||||
Type: "tcp",
|
||||
}
|
||||
}
|
||||
|
||||
nodeLogger := log.WithFields(map[string]any{
|
||||
"hop": hop,
|
||||
"kind": "node",
|
||||
"node": cfg.Name,
|
||||
"connector": cfg.Connector.Type,
|
||||
"dialer": cfg.Dialer.Type,
|
||||
})
|
||||
|
||||
serverName, _, _ := net.SplitHostPort(cfg.Addr)
|
||||
|
||||
tlsCfg := cfg.Connector.TLS
|
||||
if tlsCfg == nil {
|
||||
tlsCfg = &config.TLSConfig{}
|
||||
}
|
||||
if tlsCfg.ServerName == "" {
|
||||
tlsCfg.ServerName = serverName
|
||||
}
|
||||
tlsConfig, err := tls_util.LoadClientConfig(tlsCfg)
|
||||
if err != nil {
|
||||
nodeLogger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nm metadata.Metadata
|
||||
if cfg.Metadata != nil {
|
||||
nm = mdx.NewMetadata(cfg.Metadata)
|
||||
}
|
||||
|
||||
connectorLogger := nodeLogger.WithFields(map[string]any{
|
||||
"kind": "connector",
|
||||
})
|
||||
var cr connector.Connector
|
||||
if rf := registry.ConnectorRegistry().Get(cfg.Connector.Type); rf != nil {
|
||||
cr = rf(
|
||||
connector.AuthOption(auth_parser.Info(cfg.Connector.Auth)),
|
||||
connector.TLSConfigOption(tlsConfig),
|
||||
connector.LoggerOption(connectorLogger),
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unregistered connector: %s", cfg.Connector.Type)
|
||||
}
|
||||
|
||||
if cfg.Connector.Metadata == nil {
|
||||
cfg.Connector.Metadata = make(map[string]any)
|
||||
}
|
||||
if err := cr.Init(mdx.NewMetadata(cfg.Connector.Metadata)); err != nil {
|
||||
connectorLogger.Error("init: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsCfg = cfg.Dialer.TLS
|
||||
if tlsCfg == nil {
|
||||
tlsCfg = &config.TLSConfig{}
|
||||
}
|
||||
if tlsCfg.ServerName == "" {
|
||||
tlsCfg.ServerName = serverName
|
||||
}
|
||||
tlsConfig, err = tls_util.LoadClientConfig(tlsCfg)
|
||||
if err != nil {
|
||||
nodeLogger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ppv int
|
||||
if nm != nil {
|
||||
ppv = mdutil.GetInt(nm, parsing.MDKeyProxyProtocol)
|
||||
}
|
||||
|
||||
dialerLogger := nodeLogger.WithFields(map[string]any{
|
||||
"kind": "dialer",
|
||||
})
|
||||
|
||||
var d dialer.Dialer
|
||||
if rf := registry.DialerRegistry().Get(cfg.Dialer.Type); rf != nil {
|
||||
d = rf(
|
||||
dialer.AuthOption(auth_parser.Info(cfg.Dialer.Auth)),
|
||||
dialer.TLSConfigOption(tlsConfig),
|
||||
dialer.LoggerOption(dialerLogger),
|
||||
dialer.ProxyProtocolOption(ppv),
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unregistered dialer: %s", cfg.Dialer.Type)
|
||||
}
|
||||
|
||||
if cfg.Dialer.Metadata == nil {
|
||||
cfg.Dialer.Metadata = make(map[string]any)
|
||||
}
|
||||
if err := d.Init(mdx.NewMetadata(cfg.Dialer.Metadata)); err != nil {
|
||||
dialerLogger.Error("init: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sockOpts *chain.SockOpts
|
||||
if cfg.SockOpts != nil {
|
||||
sockOpts = &chain.SockOpts{
|
||||
Mark: cfg.SockOpts.Mark,
|
||||
}
|
||||
}
|
||||
|
||||
tr := xchain.NewTransport(d, cr,
|
||||
chain.AddrTransportOption(cfg.Addr),
|
||||
chain.InterfaceTransportOption(cfg.Interface),
|
||||
chain.NetnsTransportOption(cfg.Netns),
|
||||
chain.SockOptsTransportOption(sockOpts),
|
||||
)
|
||||
|
||||
opts := []chain.NodeOption{
|
||||
chain.TransportNodeOption(tr),
|
||||
chain.BypassNodeOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
chain.ResoloverNodeOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||
chain.HostMapperNodeOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||
chain.MetadataNodeOption(nm),
|
||||
chain.NetworkNodeOption(cfg.Network),
|
||||
}
|
||||
|
||||
if filter := cfg.Filter; filter != nil {
|
||||
// convert *.example.com to .example.com
|
||||
// convert *example.com to example.com
|
||||
host := filter.Host
|
||||
if strings.HasPrefix(host, "*") {
|
||||
host = host[1:]
|
||||
if !strings.HasPrefix(host, ".") {
|
||||
host = "." + host
|
||||
}
|
||||
}
|
||||
|
||||
settings := &chain.NodeFilterSettings{
|
||||
Protocol: filter.Protocol,
|
||||
Host: host,
|
||||
Path: filter.Path,
|
||||
}
|
||||
opts = append(opts, chain.NodeFilterOption(settings))
|
||||
}
|
||||
|
||||
if cfg.Matcher != nil {
|
||||
priority := cfg.Matcher.Priority
|
||||
|
||||
if rule := strings.TrimSpace(cfg.Matcher.Rule); rule != "" {
|
||||
if matcher, err := routing.NewMatcher(rule); err == nil {
|
||||
log.Debugf("new matcher for node %s with rule %s", cfg.Name, cfg.Matcher.Rule)
|
||||
if priority == 0 {
|
||||
priority = len(cfg.Matcher.Rule)
|
||||
}
|
||||
opts = append(opts, chain.MatcherNodeOption(matcher))
|
||||
} else {
|
||||
log.Error(err)
|
||||
priority = -1
|
||||
}
|
||||
}
|
||||
|
||||
opts = append(opts, chain.PriorityNodeOption(priority))
|
||||
}
|
||||
|
||||
if cfg.HTTP != nil {
|
||||
settings := &chain.HTTPNodeSettings{
|
||||
Host: cfg.HTTP.Host,
|
||||
RequestHeader: cfg.HTTP.RequestHeader,
|
||||
ResponseHeader: cfg.HTTP.ResponseHeader,
|
||||
}
|
||||
if settings.RequestHeader == nil {
|
||||
settings.RequestHeader = cfg.HTTP.Header
|
||||
}
|
||||
|
||||
if auth := cfg.HTTP.Auth; auth != nil && auth.Username != "" {
|
||||
settings.Auther = xauth.NewAuthenticator(
|
||||
xauth.AuthsOption(map[string]string{auth.Username: auth.Password}),
|
||||
xauth.LoggerOption(log.WithFields(map[string]any{
|
||||
"kind": "node",
|
||||
"node": cfg.Name,
|
||||
"addr": cfg.Addr,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
rewriteURL := cfg.HTTP.RewriteURL
|
||||
if rewriteURL == nil {
|
||||
rewriteURL = cfg.HTTP.Rewrite
|
||||
}
|
||||
for _, v := range rewriteURL {
|
||||
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
|
||||
settings.RewriteURL = append(settings.RewriteURL, chain.HTTPURLRewriteSetting{
|
||||
Pattern: pattern,
|
||||
Replacement: v.Replacement,
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, v := range cfg.HTTP.RewriteBody {
|
||||
if pattern, _ := regexp.Compile(v.Match); pattern != nil {
|
||||
settings.RewriteResponseBody = append(settings.RewriteResponseBody, chain.HTTPBodyRewriteSettings{
|
||||
Type: v.Type,
|
||||
Pattern: pattern,
|
||||
Replacement: []byte(v.Replacement),
|
||||
})
|
||||
}
|
||||
}
|
||||
opts = append(opts, chain.HTTPNodeOption(settings))
|
||||
}
|
||||
|
||||
if cfg.TLS != nil {
|
||||
tlsCfg := &chain.TLSNodeSettings{
|
||||
ServerName: cfg.TLS.ServerName,
|
||||
Secure: cfg.TLS.Secure,
|
||||
}
|
||||
if o := cfg.TLS.Options; o != nil {
|
||||
tlsCfg.Options.MinVersion = o.MinVersion
|
||||
tlsCfg.Options.MaxVersion = o.MaxVersion
|
||||
tlsCfg.Options.CipherSuites = o.CipherSuites
|
||||
tlsCfg.Options.ALPN = o.ALPN
|
||||
}
|
||||
opts = append(opts, chain.TLSNodeOption(tlsCfg))
|
||||
}
|
||||
return chain.NewNode(cfg.Name, cfg.Addr, opts...), nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package observer
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/observer"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
observer_plugin "github.com/go-gost/x/observer/plugin"
|
||||
)
|
||||
|
||||
func ParseObserver(cfg *config.ObserverConfig) observer.Observer {
|
||||
if cfg == nil || cfg.Plugin == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return observer_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return observer_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package parsing
|
||||
|
||||
const (
|
||||
MDKeyProxyProtocol = "proxyProtocol"
|
||||
MDKeyInterface = "interface"
|
||||
MDKeySoMark = "so_mark"
|
||||
MDKeyHash = "hash"
|
||||
MDKeyPreUp = "preUp"
|
||||
MDKeyPreDown = "preDown"
|
||||
MDKeyPostUp = "postUp"
|
||||
MDKeyPostDown = "postDown"
|
||||
MDKeyIgnoreChain = "ignoreChain"
|
||||
MDKeyEnableStats = "enableStats"
|
||||
|
||||
MDKeyRecorderDirection = "direction"
|
||||
MDKeyRecorderTimestampFormat = "timeStampFormat"
|
||||
MDKeyRecorderHexdump = "hexdump"
|
||||
MDKeyRecorderHTTPBody = "http.body"
|
||||
MDKeyRecorderHTTPMaxBodySize = "http.maxBodySize"
|
||||
|
||||
MDKeyLimiterRefreshInterval = "limiter.refreshInterval"
|
||||
MDKeyLimiterCleanupInterval = "limiter.cleanupInterval"
|
||||
MDKeyLimiterScope = "limiter.scope"
|
||||
|
||||
MDKeyObserverResetTraffic = "observer.resetTraffic"
|
||||
MDKeyObserverPeriod = "observer.period"
|
||||
|
||||
MDKeyNetns = "netns"
|
||||
MDKeyNetnsOut = "netns.out"
|
||||
|
||||
MDKeyDialTimeout = "dialTimeout"
|
||||
)
|
||||
@@ -0,0 +1,206 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/cmd"
|
||||
xmd "github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultParser = &parser{}
|
||||
)
|
||||
|
||||
func Init(args Args) {
|
||||
defaultParser = &parser{
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func Parse() (*config.Config, error) {
|
||||
return defaultParser.Parse()
|
||||
}
|
||||
|
||||
type Args struct {
|
||||
CfgFile string
|
||||
Services []string
|
||||
Nodes []string
|
||||
Debug bool
|
||||
Trace bool
|
||||
ApiAddr string
|
||||
MetricsAddr string
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
args Args
|
||||
}
|
||||
|
||||
func (p *parser) Parse() (*config.Config, error) {
|
||||
cfg := &config.Config{}
|
||||
|
||||
cfgFile := p.args.CfgFile
|
||||
if cfgFile != "" {
|
||||
cfgFile = strings.TrimSpace(cfgFile)
|
||||
if strings.HasPrefix(cfgFile, "{") && strings.HasSuffix(cfgFile, "}") {
|
||||
if err := json.Unmarshal([]byte(cfgFile), cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := cfg.ReadFile(cfgFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmdCfg, err := cmd.BuildConfigFromCmd(p.args.Services, p.args.Nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg = mergeConfig(cfg, cmdCfg)
|
||||
|
||||
if len(cfg.Services) == 0 && p.args.ApiAddr == "" && cfg.API == nil {
|
||||
if err := cfg.Load(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if v := os.Getenv("GOST_LOGGER_LEVEL"); v != "" {
|
||||
cfg.Log = &config.LogConfig{
|
||||
Level: v,
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("GOST_API"); v != "" {
|
||||
cfg.API = &config.APIConfig{
|
||||
Addr: v,
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("GOST_METRICS"); v != "" {
|
||||
cfg.Metrics = &config.MetricsConfig{
|
||||
Addr: v,
|
||||
}
|
||||
}
|
||||
if v := os.Getenv("GOST_PROFILING"); v != "" {
|
||||
cfg.Profiling = &config.ProfilingConfig{
|
||||
Addr: v,
|
||||
}
|
||||
}
|
||||
|
||||
if p.args.Debug || p.args.Trace {
|
||||
if cfg.Log == nil {
|
||||
cfg.Log = &config.LogConfig{}
|
||||
}
|
||||
|
||||
cfg.Log.Level = string(logger.DebugLevel)
|
||||
if p.args.Trace {
|
||||
cfg.Log.Level = string(logger.TraceLevel)
|
||||
}
|
||||
}
|
||||
|
||||
if p.args.ApiAddr != "" {
|
||||
cfg.API = &config.APIConfig{
|
||||
Addr: p.args.ApiAddr,
|
||||
}
|
||||
if url, _ := cmd.Norm(p.args.ApiAddr); url != nil {
|
||||
cfg.API.Addr = url.Host
|
||||
if url.User != nil {
|
||||
username := url.User.Username()
|
||||
password, _ := url.User.Password()
|
||||
cfg.API.Auth = &config.AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
m := map[string]any{}
|
||||
for k, v := range url.Query() {
|
||||
if len(v) > 0 {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
md := xmd.NewMetadata(m)
|
||||
cfg.API.PathPrefix = mdutil.GetString(md, "pathPrefix")
|
||||
cfg.API.AccessLog = mdutil.GetBool(md, "accesslog")
|
||||
}
|
||||
}
|
||||
if p.args.MetricsAddr != "" {
|
||||
cfg.Metrics = &config.MetricsConfig{
|
||||
Addr: p.args.MetricsAddr,
|
||||
}
|
||||
if url, _ := cmd.Norm(p.args.MetricsAddr); url != nil {
|
||||
cfg.Metrics.Addr = url.Host
|
||||
if url.User != nil {
|
||||
username := url.User.Username()
|
||||
password, _ := url.User.Password()
|
||||
cfg.Metrics.Auth = &config.AuthConfig{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
m := map[string]any{}
|
||||
for k, v := range url.Query() {
|
||||
if len(v) > 0 {
|
||||
m[k] = v[0]
|
||||
}
|
||||
}
|
||||
md := xmd.NewMetadata(m)
|
||||
cfg.Metrics.Path = mdutil.GetString(md, "path")
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func mergeConfig(cfg1, cfg2 *config.Config) *config.Config {
|
||||
if cfg1 == nil {
|
||||
return cfg2
|
||||
}
|
||||
if cfg2 == nil {
|
||||
return cfg1
|
||||
}
|
||||
|
||||
cfg := &config.Config{
|
||||
Services: append(cfg1.Services, cfg2.Services...),
|
||||
Chains: append(cfg1.Chains, cfg2.Chains...),
|
||||
Hops: append(cfg1.Hops, cfg2.Hops...),
|
||||
Authers: append(cfg1.Authers, cfg2.Authers...),
|
||||
Admissions: append(cfg1.Admissions, cfg2.Admissions...),
|
||||
Bypasses: append(cfg1.Bypasses, cfg2.Bypasses...),
|
||||
Resolvers: append(cfg1.Resolvers, cfg2.Resolvers...),
|
||||
Hosts: append(cfg1.Hosts, cfg2.Hosts...),
|
||||
Ingresses: append(cfg1.Ingresses, cfg2.Ingresses...),
|
||||
SDs: append(cfg1.SDs, cfg2.SDs...),
|
||||
Recorders: append(cfg1.Recorders, cfg2.Recorders...),
|
||||
Limiters: append(cfg1.Limiters, cfg2.Limiters...),
|
||||
CLimiters: append(cfg1.CLimiters, cfg2.CLimiters...),
|
||||
RLimiters: append(cfg1.RLimiters, cfg2.RLimiters...),
|
||||
Loggers: append(cfg1.Loggers, cfg2.Loggers...),
|
||||
Routers: append(cfg1.Routers, cfg2.Routers...),
|
||||
Observers: append(cfg1.Observers, cfg2.Observers...),
|
||||
TLS: cfg1.TLS,
|
||||
Log: cfg1.Log,
|
||||
API: cfg1.API,
|
||||
Metrics: cfg1.Metrics,
|
||||
Profiling: cfg1.Profiling,
|
||||
}
|
||||
if cfg2.TLS != nil {
|
||||
cfg.TLS = cfg2.TLS
|
||||
}
|
||||
if cfg2.Log != nil {
|
||||
cfg.Log = cfg2.Log
|
||||
}
|
||||
if cfg2.API != nil {
|
||||
cfg.API = cfg2.API
|
||||
}
|
||||
if cfg2.Metrics != nil {
|
||||
cfg.Metrics = cfg2.Metrics
|
||||
}
|
||||
if cfg2.Profiling != nil {
|
||||
cfg.Profiling = cfg2.Profiling
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package recorder
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
xrecorder "github.com/go-gost/x/recorder"
|
||||
recorder_plugin "github.com/go-gost/x/recorder/plugin"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
type discardCloser struct{}
|
||||
|
||||
func (discardCloser) Write(p []byte) (n int, err error) { return len(p), nil }
|
||||
func (discardCloser) Close() error { return nil }
|
||||
|
||||
func ParseRecorder(cfg *config.RecorderConfig) (r recorder.Recorder) {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return recorder_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return recorder_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
var out io.WriteCloser = discardCloser{}
|
||||
|
||||
if cfg.File.Rotation != nil {
|
||||
out = &lumberjack.Logger{
|
||||
Filename: cfg.File.Path,
|
||||
MaxSize: cfg.File.Rotation.MaxSize,
|
||||
MaxAge: cfg.File.Rotation.MaxAge,
|
||||
MaxBackups: cfg.File.Rotation.MaxBackups,
|
||||
LocalTime: cfg.File.Rotation.LocalTime,
|
||||
Compress: cfg.File.Rotation.Compress,
|
||||
}
|
||||
} else {
|
||||
os.MkdirAll(filepath.Dir(cfg.File.Path), 0755)
|
||||
f, err := os.OpenFile(cfg.File.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
logger.Default().Warn(err)
|
||||
} else {
|
||||
out = f
|
||||
}
|
||||
}
|
||||
|
||||
return xrecorder.FileRecorder(out, xrecorder.SepRecorderOption(cfg.File.Sep))
|
||||
}
|
||||
|
||||
if cfg.TCP != nil && cfg.TCP.Addr != "" {
|
||||
return xrecorder.TCPRecorder(cfg.TCP.Addr, xrecorder.TimeoutTCPRecorderOption(cfg.TCP.Timeout))
|
||||
}
|
||||
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
h := http.Header{}
|
||||
for k, v := range cfg.HTTP.Header {
|
||||
h.Add(k, v)
|
||||
}
|
||||
return xrecorder.HTTPRecorder(cfg.HTTP.URL,
|
||||
xrecorder.TimeoutHTTPRecorderOption(cfg.HTTP.Timeout),
|
||||
xrecorder.HeaderHTTPRecorderOption(h),
|
||||
)
|
||||
}
|
||||
|
||||
if cfg.Redis != nil &&
|
||||
cfg.Redis.Addr != "" &&
|
||||
cfg.Redis.Key != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // redis list
|
||||
return xrecorder.RedisListRecorder(cfg.Redis.Addr,
|
||||
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
|
||||
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
|
||||
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
|
||||
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
|
||||
)
|
||||
case "sset": // sorted set
|
||||
return xrecorder.RedisSortedSetRecorder(cfg.Redis.Addr,
|
||||
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
|
||||
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
|
||||
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
|
||||
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
|
||||
)
|
||||
default: // redis set
|
||||
return xrecorder.RedisSetRecorder(cfg.Redis.Addr,
|
||||
xrecorder.DBRedisRecorderOption(cfg.Redis.DB),
|
||||
xrecorder.KeyRedisRecorderOption(cfg.Redis.Key),
|
||||
xrecorder.UsernameRedisRecorderOption(cfg.Redis.Username),
|
||||
xrecorder.PasswordRedisRecorderOption(cfg.Redis.Password),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/resolver"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
"github.com/go-gost/x/registry"
|
||||
xresolver "github.com/go-gost/x/resolver"
|
||||
resolver_plugin "github.com/go-gost/x/resolver/plugin"
|
||||
)
|
||||
|
||||
func ParseResolver(cfg *config.ResolverConfig) (resolver.Resolver, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return resolver_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
), nil
|
||||
default:
|
||||
return resolver_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var nameservers []xresolver.NameServer
|
||||
for _, server := range cfg.Nameservers {
|
||||
nameservers = append(nameservers, xresolver.NameServer{
|
||||
Addr: server.Addr,
|
||||
Chain: registry.ChainRegistry().Get(server.Chain),
|
||||
TTL: server.TTL,
|
||||
Timeout: server.Timeout,
|
||||
ClientIP: net.ParseIP(server.ClientIP),
|
||||
Prefer: server.Prefer,
|
||||
Hostname: server.Hostname,
|
||||
Async: server.Async,
|
||||
Only: server.Only,
|
||||
})
|
||||
}
|
||||
|
||||
return xresolver.NewResolver(
|
||||
nameservers,
|
||||
xresolver.LoggerOption(
|
||||
logger.Default().WithFields(map[string]any{
|
||||
"kind": "resolver",
|
||||
"resolver": cfg.Name,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/router"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/loader"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
xrouter "github.com/go-gost/x/router"
|
||||
router_plugin "github.com/go-gost/x/router/plugin"
|
||||
)
|
||||
|
||||
func ParseRouter(cfg *config.RouterConfig) router.Router {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg.Plugin != nil {
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return router_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return router_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var routes []*router.Route
|
||||
for _, route := range cfg.Routes {
|
||||
if route == nil {
|
||||
continue
|
||||
}
|
||||
_, ipNet, _ := net.ParseCIDR(route.Net)
|
||||
dst := route.Dst
|
||||
if dst != "" {
|
||||
_, ipNet, _ = net.ParseCIDR(dst)
|
||||
} else {
|
||||
if ipNet != nil {
|
||||
dst = ipNet.String()
|
||||
}
|
||||
}
|
||||
|
||||
if dst == "" || route.Gateway == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
routes = append(routes, &router.Route{
|
||||
Net: ipNet,
|
||||
Dst: dst,
|
||||
Gateway: route.Gateway,
|
||||
})
|
||||
}
|
||||
opts := []xrouter.Option{
|
||||
xrouter.RoutesOption(routes),
|
||||
xrouter.ReloadPeriodOption(cfg.Reload),
|
||||
xrouter.LoggerOption(logger.Default().WithFields(map[string]any{
|
||||
"kind": "router",
|
||||
"router": cfg.Name,
|
||||
})),
|
||||
}
|
||||
if cfg.File != nil && cfg.File.Path != "" {
|
||||
opts = append(opts, xrouter.FileLoaderOption(loader.FileLoader(cfg.File.Path)))
|
||||
}
|
||||
if cfg.Redis != nil && cfg.Redis.Addr != "" {
|
||||
switch cfg.Redis.Type {
|
||||
case "list": // rediss list
|
||||
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisListLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
case "set": // redis set
|
||||
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisSetLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
default: // redis hash
|
||||
opts = append(opts, xrouter.RedisLoaderOption(loader.RedisHashLoader(
|
||||
cfg.Redis.Addr,
|
||||
loader.DBRedisLoaderOption(cfg.Redis.DB),
|
||||
loader.UsernameRedisLoaderOption(cfg.Redis.Username),
|
||||
loader.PasswordRedisLoaderOption(cfg.Redis.Password),
|
||||
loader.KeyRedisLoaderOption(cfg.Redis.Key),
|
||||
)))
|
||||
}
|
||||
}
|
||||
if cfg.HTTP != nil && cfg.HTTP.URL != "" {
|
||||
opts = append(opts, xrouter.HTTPLoaderOption(loader.HTTPLoader(
|
||||
cfg.HTTP.URL,
|
||||
loader.TimeoutHTTPLoaderOption(cfg.HTTP.Timeout),
|
||||
)))
|
||||
}
|
||||
return xrouter.NewRouter(opts...)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package sd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"strings"
|
||||
|
||||
"github.com/go-gost/core/sd"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/internal/plugin"
|
||||
sd_plugin "github.com/go-gost/x/sd/plugin"
|
||||
)
|
||||
|
||||
func ParseSD(cfg *config.SDConfig) sd.SD {
|
||||
if cfg == nil || cfg.Plugin == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var tlsCfg *tls.Config
|
||||
if cfg.Plugin.TLS != nil {
|
||||
tlsCfg = &tls.Config{
|
||||
ServerName: cfg.Plugin.TLS.ServerName,
|
||||
InsecureSkipVerify: !cfg.Plugin.TLS.Secure,
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(cfg.Plugin.Type) {
|
||||
case "http":
|
||||
return sd_plugin.NewHTTPPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
plugin.TimeoutOption(cfg.Plugin.Timeout),
|
||||
)
|
||||
default:
|
||||
return sd_plugin.NewGRPCPlugin(
|
||||
cfg.Name, cfg.Plugin.Addr,
|
||||
plugin.TokenOption(cfg.Plugin.Token),
|
||||
plugin.TLSConfigOption(tlsCfg),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package selector
|
||||
|
||||
import (
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/selector"
|
||||
"github.com/go-gost/x/config"
|
||||
xs "github.com/go-gost/x/selector"
|
||||
)
|
||||
|
||||
func ParseChainSelector(cfg *config.SelectorConfig) selector.Selector[chain.Chainer] {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var strategy selector.Strategy[chain.Chainer]
|
||||
switch cfg.Strategy {
|
||||
case "round", "rr":
|
||||
strategy = xs.RoundRobinStrategy[chain.Chainer]()
|
||||
case "random", "rand":
|
||||
strategy = xs.RandomStrategy[chain.Chainer]()
|
||||
case "fifo", "ha":
|
||||
strategy = xs.FIFOStrategy[chain.Chainer]()
|
||||
case "hash":
|
||||
strategy = xs.HashStrategy[chain.Chainer]()
|
||||
default:
|
||||
strategy = xs.RoundRobinStrategy[chain.Chainer]()
|
||||
}
|
||||
return xs.NewSelector(
|
||||
strategy,
|
||||
xs.FailFilter[chain.Chainer](cfg.MaxFails, cfg.FailTimeout),
|
||||
xs.BackupFilter[chain.Chainer](),
|
||||
)
|
||||
}
|
||||
|
||||
func ParseNodeSelector(cfg *config.SelectorConfig) selector.Selector[*chain.Node] {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var strategy selector.Strategy[*chain.Node]
|
||||
switch cfg.Strategy {
|
||||
case "round", "rr":
|
||||
strategy = xs.RoundRobinStrategy[*chain.Node]()
|
||||
case "random", "rand":
|
||||
strategy = xs.RandomStrategy[*chain.Node]()
|
||||
case "fifo", "ha":
|
||||
strategy = xs.FIFOStrategy[*chain.Node]()
|
||||
case "hash":
|
||||
strategy = xs.HashStrategy[*chain.Node]()
|
||||
default:
|
||||
strategy = xs.RoundRobinStrategy[*chain.Node]()
|
||||
}
|
||||
|
||||
return xs.NewSelector(
|
||||
strategy,
|
||||
xs.FailFilter[*chain.Node](cfg.MaxFails, cfg.FailTimeout),
|
||||
xs.BackupFilter[*chain.Node](),
|
||||
)
|
||||
}
|
||||
|
||||
func DefaultNodeSelector() selector.Selector[*chain.Node] {
|
||||
return xs.NewSelector(
|
||||
xs.RoundRobinStrategy[*chain.Node](),
|
||||
xs.FailFilter[*chain.Node](xs.DefaultMaxFails, xs.DefaultFailTimeout),
|
||||
xs.BackupFilter[*chain.Node](),
|
||||
)
|
||||
}
|
||||
|
||||
func DefaultChainSelector() selector.Selector[chain.Chainer] {
|
||||
return xs.NewSelector(
|
||||
xs.RoundRobinStrategy[chain.Chainer](),
|
||||
xs.FailFilter[chain.Chainer](xs.DefaultMaxFails, xs.DefaultFailTimeout),
|
||||
xs.BackupFilter[chain.Chainer](),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/auth"
|
||||
"github.com/go-gost/core/chain"
|
||||
"github.com/go-gost/core/handler"
|
||||
"github.com/go-gost/core/hop"
|
||||
"github.com/go-gost/core/listener"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/core/observer/stats"
|
||||
"github.com/go-gost/core/recorder"
|
||||
"github.com/go-gost/core/selector"
|
||||
"github.com/go-gost/core/service"
|
||||
xadmission "github.com/go-gost/x/admission"
|
||||
xauth "github.com/go-gost/x/auth"
|
||||
xbypass "github.com/go-gost/x/bypass"
|
||||
xchain "github.com/go-gost/x/chain"
|
||||
"github.com/go-gost/x/config"
|
||||
"github.com/go-gost/x/config/parsing"
|
||||
admission_parser "github.com/go-gost/x/config/parsing/admission"
|
||||
auth_parser "github.com/go-gost/x/config/parsing/auth"
|
||||
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
|
||||
hop_parser "github.com/go-gost/x/config/parsing/hop"
|
||||
logger_parser "github.com/go-gost/x/config/parsing/logger"
|
||||
selector_parser "github.com/go-gost/x/config/parsing/selector"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
cache_limiter "github.com/go-gost/x/limiter/traffic/cache"
|
||||
"github.com/go-gost/x/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
xstats "github.com/go-gost/x/observer/stats"
|
||||
"github.com/go-gost/x/registry"
|
||||
xservice "github.com/go-gost/x/service"
|
||||
"github.com/vishvananda/netns"
|
||||
)
|
||||
|
||||
func ParseService(cfg *config.ServiceConfig) (service.Service, error) {
|
||||
if cfg.Listener == nil {
|
||||
cfg.Listener = &config.ListenerConfig{}
|
||||
}
|
||||
if strings.TrimSpace(cfg.Listener.Type) == "" {
|
||||
cfg.Listener.Type = "tcp"
|
||||
}
|
||||
|
||||
if cfg.Handler == nil {
|
||||
cfg.Handler = &config.HandlerConfig{}
|
||||
}
|
||||
if strings.TrimSpace(cfg.Handler.Type) == "" {
|
||||
cfg.Handler.Type = "auto"
|
||||
}
|
||||
|
||||
log := logger.Default()
|
||||
if loggers := logger_parser.List(cfg.Logger, cfg.Loggers...); len(loggers) > 0 {
|
||||
log = logger.LoggerGroup(loggers...)
|
||||
}
|
||||
|
||||
serviceLogger := log.WithFields(map[string]any{
|
||||
"kind": "service",
|
||||
"service": cfg.Name,
|
||||
"listener": cfg.Listener.Type,
|
||||
"handler": cfg.Handler.Type,
|
||||
})
|
||||
|
||||
tlsCfg := cfg.Listener.TLS
|
||||
if tlsCfg == nil {
|
||||
tlsCfg = &config.TLSConfig{}
|
||||
}
|
||||
tlsConfig, err := tls_util.LoadServerConfig(tlsCfg)
|
||||
if err != nil {
|
||||
serviceLogger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = parsing.DefaultTLSConfig().Clone()
|
||||
tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options)
|
||||
}
|
||||
|
||||
authers := auth_parser.List(cfg.Listener.Auther, cfg.Listener.Authers...)
|
||||
if len(authers) == 0 {
|
||||
if auther := auth_parser.ParseAutherFromAuth(cfg.Listener.Auth); auther != nil {
|
||||
authers = append(authers, auther)
|
||||
}
|
||||
}
|
||||
var auther auth.Authenticator
|
||||
if len(authers) > 0 {
|
||||
auther = xauth.AuthenticatorGroup(authers...)
|
||||
}
|
||||
|
||||
admissions := admission_parser.List(cfg.Admission, cfg.Admissions...)
|
||||
|
||||
var sockOpts *chain.SockOpts
|
||||
if cfg.SockOpts != nil {
|
||||
sockOpts = &chain.SockOpts{
|
||||
Mark: cfg.SockOpts.Mark,
|
||||
}
|
||||
}
|
||||
|
||||
var ppv int
|
||||
ifce := cfg.Interface
|
||||
var preUp, preDown, postUp, postDown []string
|
||||
var ignoreChain bool
|
||||
var pStats stats.Stats
|
||||
var observerPeriod time.Duration
|
||||
var netnsIn, netnsOut string
|
||||
var dialTimeout time.Duration
|
||||
|
||||
var limiterRefreshInterval time.Duration
|
||||
var limiterCleanupInterval time.Duration
|
||||
var limiterScope string
|
||||
|
||||
if cfg.Metadata != nil {
|
||||
md := metadata.NewMetadata(cfg.Metadata)
|
||||
ppv = mdutil.GetInt(md, parsing.MDKeyProxyProtocol)
|
||||
if v := mdutil.GetString(md, parsing.MDKeyInterface); v != "" {
|
||||
ifce = v
|
||||
}
|
||||
if v := mdutil.GetInt(md, parsing.MDKeySoMark); v > 0 {
|
||||
sockOpts = &chain.SockOpts{
|
||||
Mark: v,
|
||||
}
|
||||
}
|
||||
preUp = mdutil.GetStrings(md, parsing.MDKeyPreUp)
|
||||
preDown = mdutil.GetStrings(md, parsing.MDKeyPreDown)
|
||||
postUp = mdutil.GetStrings(md, parsing.MDKeyPostUp)
|
||||
postDown = mdutil.GetStrings(md, parsing.MDKeyPostDown)
|
||||
ignoreChain = mdutil.GetBool(md, parsing.MDKeyIgnoreChain)
|
||||
|
||||
if mdutil.GetBool(md, parsing.MDKeyEnableStats) {
|
||||
pStats = xstats.NewStats(mdutil.GetBool(md, parsing.MDKeyObserverResetTraffic))
|
||||
}
|
||||
observerPeriod = mdutil.GetDuration(md, parsing.MDKeyObserverPeriod, "observePeriod")
|
||||
|
||||
netnsIn = mdutil.GetString(md, parsing.MDKeyNetns)
|
||||
netnsOut = mdutil.GetString(md, parsing.MDKeyNetnsOut)
|
||||
|
||||
dialTimeout = mdutil.GetDuration(md, parsing.MDKeyDialTimeout)
|
||||
|
||||
limiterRefreshInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterRefreshInterval)
|
||||
limiterCleanupInterval = mdutil.GetDuration(md, parsing.MDKeyLimiterCleanupInterval)
|
||||
limiterScope = mdutil.GetString(md, parsing.MDKeyLimiterScope)
|
||||
}
|
||||
|
||||
listenerLogger := serviceLogger.WithFields(map[string]any{
|
||||
"kind": "listener",
|
||||
})
|
||||
|
||||
routerOpts := []chain.RouterOption{
|
||||
chain.TimeoutRouterOption(dialTimeout),
|
||||
chain.InterfaceRouterOption(ifce),
|
||||
chain.NetnsRouterOption(netnsOut),
|
||||
chain.SockOptsRouterOption(sockOpts),
|
||||
chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||
chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||
chain.LoggerRouterOption(listenerLogger),
|
||||
}
|
||||
if !ignoreChain {
|
||||
routerOpts = append(routerOpts,
|
||||
chain.ChainRouterOption(chainGroup(cfg.Listener.Chain, cfg.Listener.ChainGroup)),
|
||||
)
|
||||
}
|
||||
|
||||
listenOpts := []listener.Option{
|
||||
listener.AddrOption(cfg.Addr),
|
||||
listener.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||
listener.AutherOption(auther),
|
||||
listener.AuthOption(auth_parser.Info(cfg.Listener.Auth)),
|
||||
listener.TLSConfigOption(tlsConfig),
|
||||
listener.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
|
||||
listener.TrafficLimiterOption(
|
||||
cache_limiter.NewCachedTrafficLimiter(
|
||||
registry.TrafficLimiterRegistry().Get(cfg.Limiter),
|
||||
cache_limiter.RefreshIntervalOption(limiterRefreshInterval),
|
||||
cache_limiter.CleanupIntervalOption(limiterCleanupInterval),
|
||||
cache_limiter.ScopeOption(limiterScope),
|
||||
),
|
||||
),
|
||||
listener.ConnLimiterOption(registry.ConnLimiterRegistry().Get(cfg.CLimiter)),
|
||||
listener.ServiceOption(cfg.Name),
|
||||
listener.ProxyProtocolOption(ppv),
|
||||
listener.StatsOption(pStats),
|
||||
listener.NetnsOption(netnsIn),
|
||||
listener.LoggerOption(listenerLogger),
|
||||
}
|
||||
|
||||
if netnsIn != "" {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
originNs, err := netns.Get()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netns.Get(): %v", err)
|
||||
}
|
||||
defer netns.Set(originNs)
|
||||
|
||||
var ns netns.NsHandle
|
||||
|
||||
if strings.HasPrefix(netnsIn, "/") {
|
||||
ns, err = netns.GetFromPath(netnsIn)
|
||||
} else {
|
||||
ns, err = netns.GetFromName(netnsIn)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("netns.Get(%s): %v", netnsIn, err)
|
||||
}
|
||||
defer ns.Close()
|
||||
|
||||
if err := netns.Set(ns); err != nil {
|
||||
return nil, fmt.Errorf("netns.Set(%s): %v", netnsIn, err)
|
||||
}
|
||||
}
|
||||
|
||||
var ln listener.Listener
|
||||
if rf := registry.ListenerRegistry().Get(cfg.Listener.Type); rf != nil {
|
||||
ln = rf(listenOpts...)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown listener: %s", cfg.Listener.Type)
|
||||
}
|
||||
|
||||
if cfg.Listener.Metadata == nil {
|
||||
cfg.Listener.Metadata = make(map[string]any)
|
||||
}
|
||||
listenerLogger.Debugf("metadata: %v", cfg.Listener.Metadata)
|
||||
if err := ln.Init(metadata.NewMetadata(cfg.Listener.Metadata)); err != nil {
|
||||
listenerLogger.Error("init: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handlerLogger := serviceLogger.WithFields(map[string]any{
|
||||
"kind": "handler",
|
||||
})
|
||||
|
||||
tlsCfg = cfg.Handler.TLS
|
||||
if tlsCfg == nil {
|
||||
tlsCfg = &config.TLSConfig{}
|
||||
}
|
||||
tlsConfig, err = tls_util.LoadServerConfig(tlsCfg)
|
||||
if err != nil {
|
||||
handlerLogger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = parsing.DefaultTLSConfig().Clone()
|
||||
tls_util.SetTLSOptions(tlsConfig, tlsCfg.Options)
|
||||
}
|
||||
|
||||
authers = auth_parser.List(cfg.Handler.Auther, cfg.Handler.Authers...)
|
||||
if len(authers) == 0 {
|
||||
if auther := auth_parser.ParseAutherFromAuth(cfg.Handler.Auth); auther != nil {
|
||||
authers = append(authers, auther)
|
||||
}
|
||||
}
|
||||
|
||||
auther = nil
|
||||
if len(authers) > 0 {
|
||||
auther = xauth.AuthenticatorGroup(authers...)
|
||||
}
|
||||
|
||||
var recorders []recorder.RecorderObject
|
||||
for _, r := range cfg.Recorders {
|
||||
md := metadata.NewMetadata(r.Metadata)
|
||||
recorders = append(recorders, recorder.RecorderObject{
|
||||
Recorder: registry.RecorderRegistry().Get(r.Name),
|
||||
Record: r.Record,
|
||||
Options: &recorder.Options{
|
||||
Direction: mdutil.GetBool(md, parsing.MDKeyRecorderDirection),
|
||||
TimestampFormat: mdutil.GetString(md, parsing.MDKeyRecorderTimestampFormat),
|
||||
Hexdump: mdutil.GetBool(md, parsing.MDKeyRecorderHexdump),
|
||||
HTTPBody: mdutil.GetBool(md, parsing.MDKeyRecorderHTTPBody),
|
||||
MaxBodySize: mdutil.GetInt(md, parsing.MDKeyRecorderHTTPMaxBodySize),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
routerOpts = []chain.RouterOption{
|
||||
chain.RetriesRouterOption(cfg.Handler.Retries),
|
||||
chain.TimeoutRouterOption(dialTimeout),
|
||||
chain.InterfaceRouterOption(ifce),
|
||||
chain.NetnsRouterOption(netnsOut),
|
||||
chain.SockOptsRouterOption(sockOpts),
|
||||
chain.ResolverRouterOption(registry.ResolverRegistry().Get(cfg.Resolver)),
|
||||
chain.HostMapperRouterOption(registry.HostsRegistry().Get(cfg.Hosts)),
|
||||
chain.RecordersRouterOption(recorders...),
|
||||
chain.LoggerRouterOption(handlerLogger),
|
||||
}
|
||||
if !ignoreChain {
|
||||
routerOpts = append(routerOpts,
|
||||
chain.ChainRouterOption(chainGroup(cfg.Handler.Chain, cfg.Handler.ChainGroup)),
|
||||
)
|
||||
}
|
||||
|
||||
var h handler.Handler
|
||||
if rf := registry.HandlerRegistry().Get(cfg.Handler.Type); rf != nil {
|
||||
h = rf(
|
||||
handler.RouterOption(xchain.NewRouter(routerOpts...)),
|
||||
handler.AutherOption(auther),
|
||||
handler.AuthOption(auth_parser.Info(cfg.Handler.Auth)),
|
||||
handler.BypassOption(xbypass.BypassGroup(bypass_parser.List(cfg.Bypass, cfg.Bypasses...)...)),
|
||||
handler.TLSConfigOption(tlsConfig),
|
||||
handler.RateLimiterOption(registry.RateLimiterRegistry().Get(cfg.RLimiter)),
|
||||
handler.TrafficLimiterOption(registry.TrafficLimiterRegistry().Get(cfg.Handler.Limiter)),
|
||||
handler.ObserverOption(registry.ObserverRegistry().Get(cfg.Handler.Observer)),
|
||||
handler.RecordersOption(recorders...),
|
||||
handler.LoggerOption(handlerLogger),
|
||||
handler.ServiceOption(cfg.Name),
|
||||
handler.NetnsOption(netnsIn),
|
||||
)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown handler: %s", cfg.Handler.Type)
|
||||
}
|
||||
|
||||
if forwarder, ok := h.(handler.Forwarder); ok {
|
||||
hop, err := parseForwarder(cfg.Forwarder, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
forwarder.Forward(hop)
|
||||
}
|
||||
|
||||
if cfg.Handler.Metadata == nil {
|
||||
cfg.Handler.Metadata = make(map[string]any)
|
||||
}
|
||||
handlerLogger.Debugf("metadata: %v", cfg.Handler.Metadata)
|
||||
if err := h.Init(metadata.NewMetadata(cfg.Handler.Metadata)); err != nil {
|
||||
handlerLogger.Error("init: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := xservice.NewService(cfg.Name, ln, h,
|
||||
xservice.AdmissionOption(xadmission.AdmissionGroup(admissions...)),
|
||||
xservice.PreUpOption(preUp),
|
||||
xservice.PreDownOption(preDown),
|
||||
xservice.PostUpOption(postUp),
|
||||
xservice.PostDownOption(postDown),
|
||||
xservice.RecordersOption(recorders...),
|
||||
xservice.StatsOption(pStats),
|
||||
xservice.ObserverOption(registry.ObserverRegistry().Get(cfg.Observer)),
|
||||
xservice.ObserverPeriodOption(observerPeriod),
|
||||
xservice.LoggerOption(serviceLogger),
|
||||
)
|
||||
|
||||
serviceLogger.Infof("listening on %s/%s", s.Addr().String(), s.Addr().Network())
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func parseForwarder(cfg *config.ForwarderConfig, log logger.Logger) (hop.Hop, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
hopName := cfg.Hop
|
||||
if hopName == "" {
|
||||
hopName = cfg.Name
|
||||
}
|
||||
if hopName != "" {
|
||||
return registry.HopRegistry().Get(hopName), nil
|
||||
}
|
||||
|
||||
hc := config.HopConfig{
|
||||
Name: cfg.Name,
|
||||
Selector: cfg.Selector,
|
||||
}
|
||||
for _, node := range cfg.Nodes {
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
filter := node.Filter
|
||||
if filter == nil {
|
||||
if node.Protocol != "" || node.Host != "" || node.Path != "" {
|
||||
filter = &config.NodeFilterConfig{
|
||||
Protocol: node.Protocol,
|
||||
Host: node.Host,
|
||||
Path: node.Path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
httpCfg := node.HTTP
|
||||
if node.Auth != nil {
|
||||
if httpCfg == nil {
|
||||
httpCfg = &config.HTTPNodeConfig{}
|
||||
}
|
||||
if httpCfg.Auth == nil {
|
||||
httpCfg.Auth = node.Auth
|
||||
}
|
||||
}
|
||||
hc.Nodes = append(hc.Nodes, &config.NodeConfig{
|
||||
Name: node.Name,
|
||||
Addr: node.Addr,
|
||||
Network: node.Network,
|
||||
Bypass: node.Bypass,
|
||||
Bypasses: node.Bypasses,
|
||||
Filter: filter,
|
||||
Matcher: node.Matcher,
|
||||
HTTP: httpCfg,
|
||||
TLS: node.TLS,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
}
|
||||
return hop_parser.ParseHop(&hc, log)
|
||||
}
|
||||
|
||||
func chainGroup(name string, group *config.ChainGroupConfig) chain.Chainer {
|
||||
var chains []chain.Chainer
|
||||
var sel selector.Selector[chain.Chainer]
|
||||
|
||||
if c := registry.ChainRegistry().Get(name); c != nil {
|
||||
chains = append(chains, c)
|
||||
}
|
||||
if group != nil {
|
||||
for _, s := range group.Chains {
|
||||
if c := registry.ChainRegistry().Get(s); c != nil {
|
||||
chains = append(chains, c)
|
||||
}
|
||||
}
|
||||
sel = selector_parser.ParseChainSelector(group.Selector)
|
||||
}
|
||||
if len(chains) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sel == nil {
|
||||
sel = selector_parser.DefaultChainSelector()
|
||||
}
|
||||
|
||||
return xchain.NewChainGroup(chains...).
|
||||
WithSelector(sel)
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package parsing
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/x/config"
|
||||
tls_util "github.com/go-gost/x/internal/util/tls"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultTLSConfig atomic.Value
|
||||
)
|
||||
|
||||
func DefaultTLSConfig() *tls.Config {
|
||||
v, _ := defaultTLSConfig.Load().(*tls.Config)
|
||||
return v
|
||||
}
|
||||
|
||||
func SetDefaultTLSConfig(cfg *tls.Config) {
|
||||
defaultTLSConfig.Store(cfg)
|
||||
}
|
||||
|
||||
func BuildDefaultTLSConfig(cfg *config.TLSConfig) (*tls.Config, error) {
|
||||
log := logger.Default()
|
||||
|
||||
if cfg == nil {
|
||||
cfg = &config.TLSConfig{
|
||||
CertFile: "cert.pem",
|
||||
KeyFile: "key.pem",
|
||||
CAFile: "ca.pem",
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig, err := tls_util.LoadDefaultConfig(cfg.CertFile, cfg.KeyFile, cfg.CAFile)
|
||||
if err != nil {
|
||||
// generate random self-signed certificate.
|
||||
cert, err := genCertificate(cfg.Validity, cfg.Organization, cfg.CommonName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
log.Debug("load global TLS certificate files failed, use random generated certificate")
|
||||
} else {
|
||||
log.Debug("load global TLS certificate files OK")
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func genCertificate(validity time.Duration, org string, cn string) (cert tls.Certificate, err error) {
|
||||
rawCert, rawKey, err := generateKeyPair(validity, org, cn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return tls.X509KeyPair(rawCert, rawKey)
|
||||
}
|
||||
|
||||
func generateKeyPair(validity time.Duration, org string, cn string) (rawCert, rawKey []byte, err error) {
|
||||
// Create private key and self-signed certificate
|
||||
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go
|
||||
|
||||
var priv crypto.PrivateKey
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if validity <= 0 {
|
||||
validity = time.Hour * 24 * 365 // one year
|
||||
}
|
||||
if org == "" {
|
||||
org = "GOST"
|
||||
}
|
||||
if cn == "" {
|
||||
cn = "gost.run"
|
||||
}
|
||||
|
||||
validFor := validity
|
||||
notBefore := time.Now()
|
||||
notAfter := notBefore.Add(validFor)
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{org},
|
||||
CommonName: cn,
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
if _, isRSA := priv.(*rsa.PrivateKey); isRSA {
|
||||
template.KeyUsage |= x509.KeyUsageKeyEncipherment
|
||||
}
|
||||
|
||||
template.DNSNames = append(template.DNSNames, cn)
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rawKey = pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func publicKey(priv crypto.PrivateKey) any {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
case *ecdsa.PrivateKey:
|
||||
return &k.PublicKey
|
||||
case ed25519.PrivateKey:
|
||||
return k.Public().(ed25519.PublicKey)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package direct
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type conn struct{}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) Read(b []byte) (n int, err error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (c *conn) Write(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
func (c *conn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
func (c *conn) RemoteAddr() net.Addr {
|
||||
return &net.TCPAddr{}
|
||||
}
|
||||
|
||||
func (c *conn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package direct
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("direct", NewConnector)
|
||||
registry.ConnectorRegistry().Register("virtual", NewConnector)
|
||||
}
|
||||
|
||||
type directConnector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &directConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *directConnector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *directConnector) Connect(ctx context.Context, _ net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
var cOpts connector.ConnectOptions
|
||||
for _, opt := range opts {
|
||||
opt(&cOpts)
|
||||
}
|
||||
|
||||
if c.md.action == "reject" {
|
||||
return &conn{}, nil
|
||||
}
|
||||
|
||||
conn, err := cOpts.Dialer.Dial(ctx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var localAddr, remoteAddr string
|
||||
if addr := conn.LocalAddr(); addr != nil {
|
||||
localAddr = addr.String()
|
||||
}
|
||||
if addr := conn.RemoteAddr(); addr != nil {
|
||||
remoteAddr = addr.String()
|
||||
}
|
||||
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": remoteAddr,
|
||||
"local": localAddr,
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package direct
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
action string
|
||||
}
|
||||
|
||||
func (c *directConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
c.md.action = strings.ToLower(mdutil.GetString(md, "action"))
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package forward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("forward", NewConnector)
|
||||
}
|
||||
|
||||
type forwardConnector struct {
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &forwardConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *forwardConnector) Init(md md.Metadata) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *forwardConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/util/socks"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("http", NewConnector)
|
||||
}
|
||||
|
||||
type httpConnector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &httpConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpConnector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"local": conn.LocalAddr().String(),
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Host: address},
|
||||
Host: address,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: c.md.header,
|
||||
}
|
||||
|
||||
if req.Header == nil {
|
||||
req.Header = http.Header{}
|
||||
}
|
||||
req.Header.Set("Proxy-Connection", "keep-alive")
|
||||
|
||||
if user := c.options.Auth; user != nil {
|
||||
u := user.Username()
|
||||
p, _ := user.Password()
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if _, ok := conn.(net.PacketConn); ok {
|
||||
err := fmt.Errorf("tcp over udp is unsupported")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
case "udp", "udp4", "udp6":
|
||||
req.Header.Set("X-Gost-Protocol", "udp")
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
if err := req.Write(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NOTE: the server may return `Transfer-Encoding: chunked` header,
|
||||
// then the Content-Length of response will be unknown (-1),
|
||||
// in this case, close body will be blocked, so we leave it untouched.
|
||||
// defer resp.Body.Close()
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s", resp.Status)
|
||||
}
|
||||
|
||||
if network == "udp" {
|
||||
addr, _ := net.ResolveUDPAddr(network, address)
|
||||
return socks.UDPTunClientConn(conn, addr), nil
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (c *httpConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "timeout"
|
||||
header = "header"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||
|
||||
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
|
||||
hd := http.Header{}
|
||||
for k, v := range mm {
|
||||
hd.Add(k, v)
|
||||
}
|
||||
c.md.header = hd
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HTTP2 connection, wrapped up just like a net.Conn.
|
||||
type http2Conn struct {
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *http2Conn) Read(b []byte) (n int, err error) {
|
||||
return c.r.Read(b)
|
||||
}
|
||||
|
||||
func (c *http2Conn) Write(b []byte) (n int, err error) {
|
||||
return c.w.Write(b)
|
||||
}
|
||||
|
||||
func (c *http2Conn) Close() (err error) {
|
||||
if r, ok := c.r.(io.Closer); ok {
|
||||
err = r.Close()
|
||||
}
|
||||
if w, ok := c.w.(io.Closer); ok {
|
||||
err = w.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *http2Conn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *http2Conn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
func (c *http2Conn) SetDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *http2Conn) SetReadDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
|
||||
func (c *http2Conn) SetWriteDeadline(t time.Time) error {
|
||||
return &net.OpError{Op: "set", Net: "http2", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("http2", NewConnector)
|
||||
}
|
||||
|
||||
type http2Connector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &http2Connector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *http2Connector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *http2Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"local": conn.LocalAddr().String(),
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
v, _ := conn.(md.Metadatable)
|
||||
if v == nil {
|
||||
err := errors.New("http2: wrong connection type")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
req := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{Scheme: "https", Host: conn.RemoteAddr().String()},
|
||||
Host: address,
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Header: c.md.header,
|
||||
Body: pr,
|
||||
// ContentLength: -1,
|
||||
}
|
||||
if req.Header == nil {
|
||||
req.Header = make(http.Header)
|
||||
}
|
||||
|
||||
if user := c.options.Auth; user != nil {
|
||||
u := user.Username()
|
||||
p, _ := user.Password()
|
||||
req.Header.Set("Proxy-Authorization",
|
||||
"Basic "+base64.StdEncoding.EncodeToString([]byte(u+":"+p)))
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpRequest(req, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
client := v.Metadata().Get("client").(*http.Client)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if log.IsLevelEnabled(logger.TraceLevel) {
|
||||
dump, _ := httputil.DumpResponse(resp, false)
|
||||
log.Trace(string(dump))
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
err = fmt.Errorf("%s", resp.Status)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hc := &http2Conn{
|
||||
r: resp.Body,
|
||||
w: pw,
|
||||
localAddr: conn.RemoteAddr(),
|
||||
}
|
||||
|
||||
hc.remoteAddr, _ = net.ResolveTCPAddr(network, address)
|
||||
|
||||
return hc, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (c *http2Connector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "timeout"
|
||||
header = "header"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||
if mm := mdutil.GetStringMapString(md, header); len(mm) > 0 {
|
||||
hd := http.Header{}
|
||||
for k, v := range mm {
|
||||
hd.Add(k, v)
|
||||
}
|
||||
c.md.header = hd
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/relay"
|
||||
"github.com/go-gost/x/internal/net/udp"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
relay_util "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
// Bind implements connector.Binder.
|
||||
func (c *relayConnector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"network": network,
|
||||
"address": address,
|
||||
})
|
||||
log.Debugf("bind on %s/%s", address, network)
|
||||
|
||||
options := connector.BindOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
return c.bindTCP(ctx, conn, network, address, log)
|
||||
case "udp", "udp4", "udp6":
|
||||
return c.bindUDP(ctx, conn, network, address, &options, log)
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *relayConnector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
||||
laddr, err := c.bind(conn, relay.CmdBind, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
|
||||
|
||||
session, err := mux.ServerSession(conn, c.md.muxCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bindListener{
|
||||
addr: laddr,
|
||||
session: session,
|
||||
logger: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *relayConnector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
|
||||
laddr, err := c.bind(conn, relay.FUDP|relay.CmdBind, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("bind on %s/%s OK", laddr, laddr.Network())
|
||||
|
||||
ln := udp.NewListener(
|
||||
relay_util.UDPTunClientPacketConn(conn),
|
||||
&udp.ListenConfig{
|
||||
Addr: laddr,
|
||||
Backlog: opts.Backlog,
|
||||
ReadQueueSize: opts.UDPDataQueueSize,
|
||||
ReadBufferSize: opts.UDPDataBufferSize,
|
||||
TTL: opts.UDPConnTTL,
|
||||
Keepalive: true,
|
||||
Logger: log,
|
||||
})
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (c *relayConnector) bind(conn net.Conn, cmd relay.CmdType, network, address string) (net.Addr, error) {
|
||||
req := relay.Request{
|
||||
Version: relay.Version1,
|
||||
Cmd: cmd,
|
||||
}
|
||||
|
||||
if c.options.Auth != nil {
|
||||
pwd, _ := c.options.Auth.Password()
|
||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
||||
Username: c.options.Auth.Username(),
|
||||
Password: pwd,
|
||||
})
|
||||
}
|
||||
|
||||
nid := relay.NetworkTCP
|
||||
if network == "udp" || network == "udp4" || network == "udp6" {
|
||||
nid = relay.NetworkUDP
|
||||
}
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: nid,
|
||||
})
|
||||
fa := &relay.AddrFeature{}
|
||||
fa.ParseFrom(address)
|
||||
req.Features = append(req.Features, fa)
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first reply, bind status
|
||||
resp := relay.Response{}
|
||||
if _, err := resp.ReadFrom(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Status != relay.StatusOK {
|
||||
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
|
||||
}
|
||||
|
||||
var addr string
|
||||
for _, f := range resp.Features {
|
||||
if f.Type() == relay.FeatureAddr {
|
||||
if fa, ok := f.(*relay.AddrFeature); ok {
|
||||
addr = net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var baddr net.Addr
|
||||
var err error
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
baddr, err = net.ResolveTCPAddr(network, addr)
|
||||
case "udp", "udp4", "udp6":
|
||||
baddr, err = net.ResolveUDPAddr(network, addr)
|
||||
default:
|
||||
err = fmt.Errorf("unknown network %s", network)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return baddr, nil
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
xrelay "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
type tcpConn struct {
|
||||
net.Conn
|
||||
wbuf *bytes.Buffer
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *tcpConn) Read(b []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
if c.wbuf != nil {
|
||||
err = readResponse(c.Conn)
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
|
||||
func (c *tcpConn) Write(b []byte) (n int, err error) {
|
||||
n = len(b) // force byte length consistent
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
||||
c.wbuf.Write(b) // append the data to the cached header
|
||||
_, err = c.Conn.Write(c.wbuf.Bytes())
|
||||
c.wbuf.Reset()
|
||||
return
|
||||
}
|
||||
_, err = c.Conn.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
type udpConn struct {
|
||||
net.Conn
|
||||
wbuf *bytes.Buffer
|
||||
once sync.Once
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *udpConn) Read(b []byte) (n int, err error) {
|
||||
c.once.Do(func() {
|
||||
if c.wbuf != nil {
|
||||
err = readResponse(c.Conn)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var bb [2]byte
|
||||
_, err = io.ReadFull(c.Conn, bb[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dlen := int(binary.BigEndian.Uint16(bb[:]))
|
||||
if len(b) >= dlen {
|
||||
return io.ReadFull(c.Conn, b[:dlen])
|
||||
}
|
||||
|
||||
buf := bufpool.Get(dlen)
|
||||
defer bufpool.Put(buf)
|
||||
_, err = io.ReadFull(c.Conn, buf)
|
||||
n = copy(b, buf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) > math.MaxUint16 {
|
||||
err = errors.New("write: data maximum exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
n = len(b)
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.wbuf != nil && c.wbuf.Len() > 0 {
|
||||
var bb [2]byte
|
||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||
c.wbuf.Write(bb[:])
|
||||
c.wbuf.Write(b) // append the data to the cached header
|
||||
_, err = c.wbuf.WriteTo(c.Conn)
|
||||
return
|
||||
}
|
||||
|
||||
var bb [2]byte
|
||||
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
|
||||
_, err = c.Conn.Write(bb[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func readResponse(r io.Reader) (err error) {
|
||||
resp := relay.Response{}
|
||||
_, err = resp.ReadFrom(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Version != relay.Version1 {
|
||||
err = relay.ErrBadVersion
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Status != relay.StatusOK {
|
||||
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type bindConn struct {
|
||||
net.Conn
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
md mdata.Metadata
|
||||
}
|
||||
|
||||
func (c *bindConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *bindConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
// Metadata implements metadata.Metadatable interface.
|
||||
func (c *bindConn) Metadata() mdata.Metadata {
|
||||
return c.md
|
||||
}
|
||||
|
||||
type bindUDPConn struct {
|
||||
net.Conn
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
md mdata.Metadata
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) Read(b []byte) (n int, err error) {
|
||||
// 2-byte data length header
|
||||
var bh [2]byte
|
||||
_, err = io.ReadFull(c.Conn, bh[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dlen := int(binary.BigEndian.Uint16(bh[:]))
|
||||
if len(b) >= dlen {
|
||||
n, err = io.ReadFull(c.Conn, b[:dlen])
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufpool.Get(dlen)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
_, err = io.ReadFull(c.Conn, buf)
|
||||
n = copy(b, buf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) > math.MaxUint16 {
|
||||
err = errors.New("write: data maximum exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufpool.Get(len(b) + 2)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
|
||||
n = copy(buf[2:], b)
|
||||
|
||||
return c.Conn.Write(buf)
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
addr = c.remoteAddr
|
||||
n, err = c.Read(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
return c.Write(b)
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *bindUDPConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
// Metadata implements metadata.Metadatable interface.
|
||||
func (c *bindUDPConn) Metadata() mdata.Metadata {
|
||||
return c.md
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
relay_util "github.com/go-gost/x/internal/util/relay"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("relay", NewConnector)
|
||||
}
|
||||
|
||||
type relayConnector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &relayConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *relayConnector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *relayConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
req := relay.Request{
|
||||
Version: relay.Version1,
|
||||
Cmd: relay.CmdConnect,
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "udp", "udp4", "udp6":
|
||||
req.Cmd |= relay.FUDP
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: relay.NetworkUDP,
|
||||
})
|
||||
|
||||
// UDP association
|
||||
if address == "" {
|
||||
baddr, err := c.bind(conn, relay.FUDP|relay.CmdBind, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("associate on %s OK", baddr)
|
||||
|
||||
return relay_util.UDPTunClientConn(conn, nil), nil
|
||||
}
|
||||
|
||||
case "unix":
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: relay.NetworkUnix,
|
||||
})
|
||||
case "serial":
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: relay.NetworkSerial,
|
||||
})
|
||||
}
|
||||
|
||||
if c.options.Auth != nil {
|
||||
pwd, _ := c.options.Auth.Password()
|
||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
||||
Username: c.options.Auth.Username(),
|
||||
Password: pwd,
|
||||
})
|
||||
}
|
||||
|
||||
if address != "" {
|
||||
af := &relay.AddrFeature{}
|
||||
if err := af.ParseFrom(address); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Features = append(req.Features, af)
|
||||
}
|
||||
|
||||
if c.md.noDelay {
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// drain the response
|
||||
if err := readResponse(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6", "unix", "serial":
|
||||
if !c.md.noDelay {
|
||||
cc := &tcpConn{
|
||||
Conn: conn,
|
||||
wbuf: &bytes.Buffer{},
|
||||
}
|
||||
if _, err := req.WriteTo(cc.wbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn = cc
|
||||
}
|
||||
case "udp", "udp4", "udp6":
|
||||
cc := &udpConn{
|
||||
Conn: conn,
|
||||
}
|
||||
if !c.md.noDelay {
|
||||
cc.wbuf = &bytes.Buffer{}
|
||||
if _, err := req.WriteTo(cc.wbuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn = cc
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
mdx "github.com/go-gost/x/metadata"
|
||||
)
|
||||
|
||||
type bindListener struct {
|
||||
network string
|
||||
addr net.Addr
|
||||
session *mux.Session
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (p *bindListener) Accept() (net.Conn, error) {
|
||||
cc, err := p.session.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := p.getPeerConn(cc)
|
||||
if err != nil {
|
||||
cc.Close()
|
||||
p.logger.Errorf("get peer failed: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (p *bindListener) getPeerConn(conn net.Conn) (net.Conn, error) {
|
||||
// second reply, peer connected
|
||||
resp := relay.Response{}
|
||||
if _, err := resp.ReadFrom(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Status != relay.StatusOK {
|
||||
err := fmt.Errorf("peer connect failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var address, host string
|
||||
// the first addr is the client address, the optional second addr is the target host address.
|
||||
for _, f := range resp.Features {
|
||||
if f.Type() == relay.FeatureAddr {
|
||||
if fa, ok := f.(*relay.AddrFeature); ok {
|
||||
v := net.JoinHostPort(fa.Host, strconv.Itoa(int(fa.Port)))
|
||||
if address != "" {
|
||||
host = v
|
||||
} else {
|
||||
address = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveTCPAddr("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var md mdata.Metadata
|
||||
if host != "" {
|
||||
md = mdx.NewMetadata(map[string]any{"host": host})
|
||||
}
|
||||
|
||||
if p.network == "udp" {
|
||||
return &bindUDPConn{
|
||||
Conn: conn,
|
||||
localAddr: p.addr,
|
||||
remoteAddr: raddr,
|
||||
md: md,
|
||||
}, nil
|
||||
}
|
||||
|
||||
cn := &bindConn{
|
||||
Conn: conn,
|
||||
localAddr: p.addr,
|
||||
remoteAddr: raddr,
|
||||
md: md,
|
||||
}
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
func (p *bindListener) Addr() net.Addr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *bindListener) Close() error {
|
||||
return p.session.Close()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
noDelay bool
|
||||
muxCfg *mux.Config
|
||||
}
|
||||
|
||||
func (c *relayConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "connectTimeout"
|
||||
noDelay = "nodelay"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||
c.md.noDelay = mdutil.GetBool(md, noDelay)
|
||||
|
||||
c.md.muxCfg = &mux.Config{
|
||||
Version: mdutil.GetInt(md, "mux.version"),
|
||||
KeepAliveInterval: mdutil.GetDuration(md, "mux.keepaliveInterval"),
|
||||
KeepAliveDisabled: mdutil.GetBool(md, "mux.keepaliveDisabled"),
|
||||
KeepAliveTimeout: mdutil.GetDuration(md, "mux.keepaliveTimeout"),
|
||||
MaxFrameSize: mdutil.GetInt(md, "mux.maxFrameSize"),
|
||||
MaxReceiveBuffer: mdutil.GetInt(md, "mux.maxReceiveBuffer"),
|
||||
MaxStreamBuffer: mdutil.GetInt(md, "mux.maxStreamBuffer"),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
"github.com/go-gost/relay"
|
||||
xrelay "github.com/go-gost/x/internal/util/relay"
|
||||
)
|
||||
|
||||
type packetConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *packetConn) Read(b []byte) (n int, err error) {
|
||||
var bb [2]byte
|
||||
_, err = io.ReadFull(c.Conn, bb[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dlen := int(binary.BigEndian.Uint16(bb[:]))
|
||||
if len(b) >= dlen {
|
||||
return io.ReadFull(c.Conn, b[:dlen])
|
||||
}
|
||||
|
||||
buf := bufpool.Get(dlen)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
n, err = io.ReadFull(c.Conn, buf)
|
||||
copy(b, buf[:n])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *packetConn) Write(b []byte) (n int, err error) {
|
||||
if len(b) > math.MaxUint16 {
|
||||
err = errors.New("write: data maximum exceeded")
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufpool.Get(len(b) + 2)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
|
||||
n = copy(buf[2:], b)
|
||||
|
||||
return c.Conn.Write(buf)
|
||||
}
|
||||
|
||||
func readResponse(r io.Reader) (err error) {
|
||||
resp := relay.Response{}
|
||||
_, err = resp.ReadFrom(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Version != relay.Version1 {
|
||||
err = relay.ErrBadVersion
|
||||
return
|
||||
}
|
||||
|
||||
if resp.Status != relay.StatusOK {
|
||||
err = fmt.Errorf("%d %s", resp.Status, xrelay.StatusText(resp.Status))
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("router", NewConnector)
|
||||
}
|
||||
|
||||
type routerConnector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &routerConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *routerConnector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *routerConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
req := relay.Request{
|
||||
Version: relay.Version1,
|
||||
Cmd: relay.CmdAssociate,
|
||||
}
|
||||
|
||||
if c.options.Auth != nil {
|
||||
pwd, _ := c.options.Auth.Password()
|
||||
req.Features = append(req.Features, &relay.UserAuthFeature{
|
||||
Username: c.options.Auth.Username(),
|
||||
Password: pwd,
|
||||
})
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "udp":
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: relay.NetworkUDP,
|
||||
})
|
||||
|
||||
case "ip":
|
||||
req.Features = append(req.Features, &relay.NetworkFeature{
|
||||
Network: relay.NetworkIP,
|
||||
})
|
||||
}
|
||||
|
||||
srcAddr := conn.LocalAddr().String()
|
||||
if v := ctxvalue.ClientAddrFromContext(ctx); v != "" {
|
||||
srcAddr = string(v)
|
||||
}
|
||||
|
||||
af := &relay.AddrFeature{}
|
||||
af.ParseFrom(srcAddr)
|
||||
req.Features = append(req.Features, af) // src address
|
||||
|
||||
af = &relay.AddrFeature{}
|
||||
af.ParseFrom(address)
|
||||
req.Features = append(req.Features, af) // dst address
|
||||
|
||||
if !c.md.routerID.IsZero() {
|
||||
req.Features = append(req.Features, &relay.TunnelFeature{
|
||||
ID: c.md.routerID,
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := req.WriteTo(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// drain the response
|
||||
if err := readResponse(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "udp", "ip":
|
||||
conn = &packetConn{
|
||||
Conn: conn,
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("connect to router %s@%s OK", address, c.md.routerID)
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/relay"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRouterID = errors.New("router: invalid router ID")
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
routerID relay.TunnelID
|
||||
}
|
||||
|
||||
func (c *routerConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, "connectTimeout")
|
||||
|
||||
if s := mdutil.GetString(md, "router.id"); s != "" {
|
||||
uuid, err := uuid.Parse(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.md.routerID = relay.NewTunnelID(uuid[:])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("serial", NewConnector)
|
||||
}
|
||||
|
||||
type serialConnector struct {
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &serialConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serialConnector) Init(md md.Metadata) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serialConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package sni
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
dissector "github.com/go-gost/tls-dissector"
|
||||
)
|
||||
|
||||
type sniClientConn struct {
|
||||
host string
|
||||
obfuscated bool
|
||||
net.Conn
|
||||
}
|
||||
|
||||
func (c *sniClientConn) Write(p []byte) (int, error) {
|
||||
b, err := c.obfuscate(p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = c.Conn.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c *sniClientConn) obfuscate(p []byte) ([]byte, error) {
|
||||
if c.host == "" {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
if c.obfuscated {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
if p[0] == dissector.Handshake {
|
||||
b, err := readClientHelloRecord(bytes.NewReader(p), c.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.obfuscated = true
|
||||
return b, nil
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
br := bufio.NewReader(bytes.NewReader(p))
|
||||
for {
|
||||
s, err := br.ReadString('\n')
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
if s != "" {
|
||||
buf.Write([]byte(s))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// end of HTTP header
|
||||
if s == "\r\n" {
|
||||
buf.Write([]byte(s))
|
||||
// drain the remain bytes.
|
||||
io.Copy(buf, br)
|
||||
break
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "Host") {
|
||||
s = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(s, "Host:"), "\r\n"))
|
||||
host := encodeServerName(s)
|
||||
buf.WriteString("Host: " + c.host + "\r\n")
|
||||
buf.WriteString("Gost-Target: " + host + "\r\n")
|
||||
// drain the remain bytes.
|
||||
io.Copy(buf, br)
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(s))
|
||||
}
|
||||
c.obfuscated = true
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func readClientHelloRecord(r io.Reader, host string) ([]byte, error) {
|
||||
record, err := dissector.ReadRecord(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientHello := dissector.ClientHelloMsg{}
|
||||
if err := clientHello.Decode(record.Opaque); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ext := range clientHello.Extensions {
|
||||
if ext.Type() == dissector.ExtServerName {
|
||||
snExtension := ext.(*dissector.ServerNameExtension)
|
||||
if host != "" {
|
||||
e, _ := dissector.NewExtension(0xFFFE, []byte(encodeServerName(snExtension.Name)))
|
||||
clientHello.Extensions = append(clientHello.Extensions, e)
|
||||
snExtension.Name = host
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
record.Opaque, err = clientHello.Encode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
if _, err := record.WriteTo(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func encodeServerName(name string) string {
|
||||
buf := &bytes.Buffer{}
|
||||
binary.Write(buf, binary.BigEndian, crc32.ChecksumIEEE([]byte(name)))
|
||||
buf.WriteString(base64.RawURLEncoding.EncodeToString([]byte(name)))
|
||||
return base64.RawURLEncoding.EncodeToString(buf.Bytes())
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package sni
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("sni", NewConnector)
|
||||
}
|
||||
|
||||
type sniConnector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &sniConnector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sniConnector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *sniConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
return &sniClientConn{Conn: conn, host: c.md.host}, nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package sni
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
host string
|
||||
connectTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *sniConnector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
host = "host"
|
||||
connectTimeout = "timeout"
|
||||
)
|
||||
|
||||
c.md.host = mdutil.GetString(md, host)
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/gosocks4"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("socks4", NewConnector)
|
||||
registry.ConnectorRegistry().Register("socks4a", NewConnector)
|
||||
}
|
||||
|
||||
type socks4Connector struct {
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &socks4Connector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socks4Connector) Init(md md.Metadata) (err error) {
|
||||
return c.parseMetadata(md)
|
||||
}
|
||||
|
||||
func (c *socks4Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if _, ok := conn.(net.PacketConn); ok {
|
||||
err := fmt.Errorf("tcp over udp is unsupported")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var addr *gosocks4.Addr
|
||||
|
||||
if c.md.disable4a {
|
||||
taddr, err := net.ResolveTCPAddr("tcp4", address)
|
||||
if err != nil {
|
||||
log.Error("resolve: ", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(taddr.IP) == 0 {
|
||||
taddr.IP = net.IPv4zero
|
||||
}
|
||||
addr = &gosocks4.Addr{
|
||||
Type: gosocks4.AddrIPv4,
|
||||
Host: taddr.IP.String(),
|
||||
Port: uint16(taddr.Port),
|
||||
}
|
||||
} else {
|
||||
host, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, _ := strconv.Atoi(port)
|
||||
addr = &gosocks4.Addr{
|
||||
Type: gosocks4.AddrDomain,
|
||||
Host: host,
|
||||
Port: uint16(p),
|
||||
}
|
||||
}
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
var userid []byte
|
||||
if c.options.Auth != nil {
|
||||
userid = []byte(c.options.Auth.Username())
|
||||
}
|
||||
req := gosocks4.NewRequest(gosocks4.CmdConnect, addr, userid)
|
||||
log.Trace(req)
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := gosocks4.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
if reply.Code != gosocks4.Granted {
|
||||
err = errors.New("host unreachable")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mdata "github.com/go-gost/core/metadata"
|
||||
mdutil "github.com/go-gost/x/metadata/util"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
connectTimeout time.Duration
|
||||
disable4a bool
|
||||
}
|
||||
|
||||
func (c *socks4Connector) parseMetadata(md mdata.Metadata) (err error) {
|
||||
const (
|
||||
connectTimeout = "timeout"
|
||||
disable4a = "disable4a"
|
||||
)
|
||||
|
||||
c.md.connectTimeout = mdutil.GetDuration(md, connectTimeout)
|
||||
c.md.disable4a = mdutil.GetBool(md, disable4a)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/x/internal/net/udp"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
"github.com/go-gost/x/internal/util/socks"
|
||||
)
|
||||
|
||||
// Bind implements connector.Binder.
|
||||
func (c *socks5Connector) Bind(ctx context.Context, conn net.Conn, network, address string, opts ...connector.BindOption) (net.Listener, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
})
|
||||
log.Debugf("bind on %s/%s", address, network)
|
||||
|
||||
options := connector.BindOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if options.Mux {
|
||||
return c.muxBindTCP(ctx, conn, network, address, log)
|
||||
}
|
||||
return c.bindTCP(ctx, conn, network, address, log)
|
||||
case "udp", "udp4", "udp6":
|
||||
return c.bindUDP(ctx, conn, network, address, &options, log)
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socks5Connector) bindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
||||
laddr, err := c.bind(conn, gosocks5.CmdBind, network, address, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tcpListener{
|
||||
addr: laddr,
|
||||
conn: conn,
|
||||
logger: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *socks5Connector) muxBindTCP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (net.Listener, error) {
|
||||
laddr, err := c.bind(conn, socks.CmdMuxBind, network, address, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := mux.ServerSession(conn, c.md.muxCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tcpMuxListener{
|
||||
addr: laddr,
|
||||
session: session,
|
||||
logger: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *socks5Connector) bindUDP(ctx context.Context, conn net.Conn, network, address string, opts *connector.BindOptions, log logger.Logger) (net.Listener, error) {
|
||||
laddr, err := c.bind(conn, socks.CmdUDPTun, network, address, log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln := udp.NewListener(socks.UDPTunClientPacketConn(conn),
|
||||
&udp.ListenConfig{
|
||||
Addr: laddr,
|
||||
Backlog: opts.Backlog,
|
||||
ReadQueueSize: opts.UDPDataQueueSize,
|
||||
ReadBufferSize: opts.UDPDataBufferSize,
|
||||
TTL: opts.UDPConnTTL,
|
||||
Keepalive: true,
|
||||
Logger: log,
|
||||
})
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func (l *socks5Connector) bind(conn net.Conn, cmd uint8, network, address string, log logger.Logger) (net.Addr, error) {
|
||||
addr := gosocks5.Addr{}
|
||||
addr.ParseFrom(address)
|
||||
req := gosocks5.NewRequest(cmd, &addr)
|
||||
log.Trace(req)
|
||||
if err := req.Write(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// first reply, bind status
|
||||
reply, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
return nil, fmt.Errorf("bind on %s/%s failed", address, network)
|
||||
}
|
||||
|
||||
var baddr net.Addr
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
baddr, err = net.ResolveTCPAddr(network, reply.Addr.String())
|
||||
case "udp", "udp4", "udp6":
|
||||
baddr, err = net.ResolveUDPAddr(network, reply.Addr.String())
|
||||
default:
|
||||
err = fmt.Errorf("unknown network %s", network)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("bind on %s/%s OK", baddr, baddr.Network())
|
||||
|
||||
return baddr, nil
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/common/bufpool"
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/gosocks5"
|
||||
)
|
||||
|
||||
type bindConn struct {
|
||||
net.Conn
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *bindConn) LocalAddr() net.Addr {
|
||||
return c.localAddr
|
||||
}
|
||||
|
||||
func (c *bindConn) RemoteAddr() net.Addr {
|
||||
return c.remoteAddr
|
||||
}
|
||||
|
||||
type udpRelayConn struct {
|
||||
udpConn *net.UDPConn
|
||||
tcpConn net.Conn
|
||||
taddr net.Addr
|
||||
bufferSize int
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
|
||||
buf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
nn, err := c.udpConn.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
socksAddr := gosocks5.Addr{}
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
}
|
||||
_, err = dgram.ReadFrom(bytes.NewReader(buf[:nn]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n = copy(b, dgram.Data)
|
||||
addr, err = net.ResolveUDPAddr("udp", header.Addr.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) Read(b []byte) (n int, err error) {
|
||||
n, _, err = c.ReadFrom(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
|
||||
socksAddr := gosocks5.Addr{}
|
||||
if err = socksAddr.ParseFrom(addr.String()); err != nil {
|
||||
return
|
||||
}
|
||||
if socksAddr.Host == "" {
|
||||
socksAddr.Type = gosocks5.AddrIPv4
|
||||
socksAddr.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
header := gosocks5.UDPHeader{
|
||||
Addr: &socksAddr,
|
||||
}
|
||||
dgram := gosocks5.UDPDatagram{
|
||||
Header: &header,
|
||||
Data: b,
|
||||
}
|
||||
|
||||
buf := bufpool.Get(c.bufferSize)
|
||||
defer bufpool.Put(buf)
|
||||
|
||||
nn, err := dgram.WriteTo(bytes.NewBuffer(buf[:0]))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if nn > int64(len(buf)) {
|
||||
nn = int64(len(buf))
|
||||
}
|
||||
|
||||
_, err = c.udpConn.Write(buf[:nn])
|
||||
n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) Write(b []byte) (n int, err error) {
|
||||
return c.WriteTo(b, c.taddr)
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) RemoteAddr() net.Addr {
|
||||
return c.taddr
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) LocalAddr() net.Addr {
|
||||
return c.udpConn.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) Close() error {
|
||||
c.udpConn.Close()
|
||||
return c.tcpConn.Close()
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) SetDeadline(t time.Time) error {
|
||||
return c.udpConn.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) SetReadDeadline(t time.Time) error {
|
||||
return c.udpConn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *udpRelayConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.udpConn.SetWriteDeadline(t)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-gost/core/connector"
|
||||
"github.com/go-gost/core/logger"
|
||||
md "github.com/go-gost/core/metadata"
|
||||
"github.com/go-gost/gosocks5"
|
||||
ctxvalue "github.com/go-gost/x/ctx"
|
||||
"github.com/go-gost/x/internal/util/socks"
|
||||
"github.com/go-gost/x/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.ConnectorRegistry().Register("socks5", NewConnector)
|
||||
registry.ConnectorRegistry().Register("socks", NewConnector)
|
||||
}
|
||||
|
||||
type socks5Connector struct {
|
||||
selector gosocks5.Selector
|
||||
md metadata
|
||||
options connector.Options
|
||||
}
|
||||
|
||||
func NewConnector(opts ...connector.Option) connector.Connector {
|
||||
options := connector.Options{}
|
||||
for _, opt := range opts {
|
||||
opt(&options)
|
||||
}
|
||||
|
||||
return &socks5Connector{
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socks5Connector) Init(md md.Metadata) (err error) {
|
||||
if err = c.parseMetadata(md); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
selector := &clientSelector{
|
||||
methods: []uint8{
|
||||
gosocks5.MethodNoAuth,
|
||||
},
|
||||
User: c.options.Auth,
|
||||
TLSConfig: c.options.TLSConfig,
|
||||
logger: c.options.Logger,
|
||||
}
|
||||
if selector.User != nil {
|
||||
selector.methods = append(selector.methods, gosocks5.MethodUserPass)
|
||||
}
|
||||
if !c.md.noTLS {
|
||||
selector.methods = append(selector.methods, socks.MethodTLS)
|
||||
if selector.TLSConfig == nil {
|
||||
selector.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
if selector.User != nil {
|
||||
selector.methods = append(selector.methods, socks.MethodTLSAuth)
|
||||
}
|
||||
}
|
||||
c.selector = selector
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Handshake implements connector.Handshaker.
|
||||
func (c *socks5Connector) Handshake(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
})
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
cc := gosocks5.ClientConn(conn, c.selector)
|
||||
if err := cc.Handleshake(); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (c *socks5Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
|
||||
log := c.options.Logger.WithFields(map[string]any{
|
||||
"remote": conn.RemoteAddr().String(),
|
||||
"local": conn.LocalAddr().String(),
|
||||
"network": network,
|
||||
"address": address,
|
||||
"sid": string(ctxvalue.SidFromContext(ctx)),
|
||||
})
|
||||
log.Debugf("connect %s/%s", address, network)
|
||||
|
||||
if c.md.connectTimeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(c.md.connectTimeout))
|
||||
defer conn.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
var cOpts connector.ConnectOptions
|
||||
for _, opt := range opts {
|
||||
opt(&cOpts)
|
||||
}
|
||||
|
||||
switch network {
|
||||
case "udp", "udp4", "udp6":
|
||||
return c.connectUDP(ctx, conn, network, address, log, &cOpts)
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
if _, ok := conn.(net.PacketConn); ok {
|
||||
err := fmt.Errorf("tcp over udp is unsupported")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
err := fmt.Errorf("network %s is unsupported", network)
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := gosocks5.Addr{}
|
||||
if err := addr.ParseFrom(address); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if addr.Host == "" {
|
||||
addr.Type = gosocks5.AddrIPv4
|
||||
addr.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
req := gosocks5.NewRequest(gosocks5.CmdConnect, &addr)
|
||||
log.Trace(req)
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
err = errors.New("host unreachable")
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *socks5Connector) connectUDP(ctx context.Context, conn net.Conn, network, address string, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
|
||||
addr, err := net.ResolveUDPAddr(network, address)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c.md.relay == "udp" {
|
||||
return c.relayUDP(ctx, conn, addr, log, opts)
|
||||
}
|
||||
|
||||
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
|
||||
log.Trace(req)
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
return nil, errors.New("get socks5 UDP tunnel failure")
|
||||
}
|
||||
|
||||
return socks.UDPTunClientConn(conn, addr), nil
|
||||
}
|
||||
|
||||
func (c *socks5Connector) relayUDP(ctx context.Context, conn net.Conn, addr net.Addr, log logger.Logger, opts *connector.ConnectOptions) (net.Conn, error) {
|
||||
req := gosocks5.NewRequest(gosocks5.CmdUdp, nil)
|
||||
log.Trace(req)
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reply, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
log.Trace(reply)
|
||||
|
||||
if reply.Rep != gosocks5.Succeeded {
|
||||
return nil, errors.New("get socks5 UDP tunnel failure")
|
||||
}
|
||||
|
||||
log.Debugf("bind on: %v", reply.Addr)
|
||||
|
||||
cc, err := opts.Dialer.Dial(ctx, "udp", reply.Addr.String())
|
||||
if err != nil {
|
||||
c.options.Logger.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("%s <- %s -> %s", cc.LocalAddr(), cc.RemoteAddr(), addr)
|
||||
|
||||
if c.md.udpTimeout > 0 {
|
||||
cc.SetReadDeadline(time.Now().Add(c.md.udpTimeout))
|
||||
}
|
||||
|
||||
return &udpRelayConn{
|
||||
udpConn: cc.(*net.UDPConn),
|
||||
tcpConn: conn,
|
||||
taddr: addr,
|
||||
bufferSize: c.md.udpBufferSize,
|
||||
logger: log,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package v5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/go-gost/core/logger"
|
||||
"github.com/go-gost/gosocks5"
|
||||
"github.com/go-gost/x/internal/util/mux"
|
||||
)
|
||||
|
||||
type tcpListener struct {
|
||||
addr net.Addr
|
||||
conn net.Conn
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (p *tcpListener) Accept() (net.Conn, error) {
|
||||
// second reply, peer connected
|
||||
rep, err := gosocks5.ReadReply(p.conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.logger.Trace(rep)
|
||||
|
||||
if rep.Rep != gosocks5.Succeeded {
|
||||
return nil, fmt.Errorf("peer connect failed")
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bindConn{
|
||||
Conn: p.conn,
|
||||
localAddr: p.addr,
|
||||
remoteAddr: raddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *tcpListener) Addr() net.Addr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *tcpListener) Close() error {
|
||||
return p.conn.Close()
|
||||
}
|
||||
|
||||
type tcpMuxListener struct {
|
||||
addr net.Addr
|
||||
session *mux.Session
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (p *tcpMuxListener) Accept() (net.Conn, error) {
|
||||
cc, err := p.session.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := p.getPeerConn(cc)
|
||||
if err != nil {
|
||||
cc.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (p *tcpMuxListener) getPeerConn(conn net.Conn) (net.Conn, error) {
|
||||
// second reply, peer connected
|
||||
rep, err := gosocks5.ReadReply(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.logger.Trace(rep)
|
||||
|
||||
if rep.Rep != gosocks5.Succeeded {
|
||||
err = fmt.Errorf("peer connect failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raddr, err := net.ResolveTCPAddr("tcp", rep.Addr.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bindConn{
|
||||
Conn: conn,
|
||||
localAddr: p.addr,
|
||||
remoteAddr: raddr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *tcpMuxListener) Addr() net.Addr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
func (p *tcpMuxListener) Close() error {
|
||||
return p.session.Close()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user