mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-12 12:16:37 +08:00
init
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user