This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
@@ -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
}
+122
View File
@@ -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
}
+88
View File
@@ -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
}
+52
View File
@@ -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
}
+145
View File
@@ -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
}
+99
View File
@@ -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...)
}
+94
View File
@@ -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...)
}
+186
View File
@@ -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...)
}
+70
View File
@@ -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
}
+257
View File
@@ -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),
)
}
}
+32
View File
@@ -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"
)
+206
View File
@@ -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
}
+123
View File
@@ -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,
}),
),
)
}
+117
View File
@@ -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...)
}
+39
View File
@@ -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](),
)
}
+432
View File
@@ -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)
}
+149
View File
@@ -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
}
}