diff --git a/client/admin_api.go b/client/admin_api.go index 3977df1..c5548fb 100644 --- a/client/admin_api.go +++ b/client/admin_api.go @@ -62,7 +62,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) { return } - pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(svr.cfg.User, content, newCommonCfg.Start) + pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(svr.cfg.User, content, newCommonCfg.Start) if err != nil { res.Code = 400 res.Msg = err.Error() @@ -243,7 +243,7 @@ func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) { return } - rows := strings.Split(content, "\n") + rows := strings.Split(string(content), "\n") newRows := make([]string, 0, len(rows)) for _, row := range rows { row = strings.TrimSpace(row) diff --git a/client/proxy/proxy.go b/client/proxy/proxy.go index 3e97784..da89720 100644 --- a/client/proxy/proxy.go +++ b/client/proxy/proxy.go @@ -148,7 +148,7 @@ func (pxy *TCPProxy) Close() { } func (pxy *TCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } @@ -177,7 +177,7 @@ func (pxy *TCPMuxProxy) Close() { } func (pxy *TCPMuxProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } @@ -206,7 +206,7 @@ func (pxy *HTTPProxy) Close() { } func (pxy *HTTPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } @@ -235,7 +235,7 @@ func (pxy *HTTPSProxy) Close() { } func (pxy *HTTPSProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } @@ -264,7 +264,7 @@ func (pxy *STCPProxy) Close() { } func (pxy *STCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, conn, []byte(pxy.clientCfg.Token), m) } @@ -410,7 +410,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, m *msg.StartWorkConn) { return } - HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, pxy.limiter, + HandleTCPWorkConnection(pxy.ctx, &pxy.cfg.LocalSvrConf, pxy.proxyPlugin, pxy.cfg.GetBaseInfo(), pxy.limiter, muxConn, []byte(pxy.cfg.Sk), m) } diff --git a/cmd/frpc/sub/http.go b/cmd/frpc/sub/http.go index d1286b2..03593f1 100644 --- a/cmd/frpc/sub/http.go +++ b/cmd/frpc/sub/http.go @@ -47,7 +47,7 @@ var httpCmd = &cobra.Command{ Use: "http", Short: "Run frpc with a single http proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/https.go b/cmd/frpc/sub/https.go index 99a2295..d636f42 100644 --- a/cmd/frpc/sub/https.go +++ b/cmd/frpc/sub/https.go @@ -43,7 +43,7 @@ var httpsCmd = &cobra.Command{ Use: "https", Short: "Run frpc with a single https proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/root.go b/cmd/frpc/sub/root.go index a085dbc..bf86793 100644 --- a/cmd/frpc/sub/root.go +++ b/cmd/frpc/sub/root.go @@ -129,9 +129,9 @@ func handleSignal(svr *client.Service) { close(kcpDoneCh) } -func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommonConf, err error) { +func parseClientCommonCfg(fileType int, source []byte) (cfg config.ClientCommonConf, err error) { if fileType == CfgFileTypeIni { - cfg, err = parseClientCommonCfgFromIni(content) + cfg, err = config.UnmarshalClientConfFromIni(source) } else if fileType == CfgFileTypeCmd { cfg, err = parseClientCommonCfgFromCmd() } @@ -146,14 +146,6 @@ func parseClientCommonCfg(fileType int, content string) (cfg config.ClientCommon return } -func parseClientCommonCfgFromIni(content string) (config.ClientCommonConf, error) { - cfg, err := config.UnmarshalClientConfFromIni(content) - if err != nil { - return config.ClientCommonConf{}, err - } - return cfg, err -} - func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { cfg = config.GetDefaultClientConf() @@ -191,7 +183,7 @@ func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) { } func runClient(cfgFilePath string) (err error) { - var content string + var content []byte content, err = config.GetRenderedConfFromFile(cfgFilePath) if err != nil { return @@ -202,9 +194,9 @@ func runClient(cfgFilePath string) (err error) { return } - pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(cfg.User, content, cfg.Start) + pxyCfgs, visitorCfgs, err := config.LoadAllProxyConfsFromIni(cfg.User, content, cfg.Start) if err != nil { - return err + return } err = startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath) diff --git a/cmd/frpc/sub/stcp.go b/cmd/frpc/sub/stcp.go index 1b4ac0f..673a268 100644 --- a/cmd/frpc/sub/stcp.go +++ b/cmd/frpc/sub/stcp.go @@ -45,7 +45,7 @@ var stcpCmd = &cobra.Command{ Use: "stcp", Short: "Run frpc with a single stcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/sudp.go b/cmd/frpc/sub/sudp.go index d730677..3c3d5a8 100644 --- a/cmd/frpc/sub/sudp.go +++ b/cmd/frpc/sub/sudp.go @@ -45,7 +45,7 @@ var sudpCmd = &cobra.Command{ Use: "sudp", Short: "Run frpc with a single sudp proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/tcp.go b/cmd/frpc/sub/tcp.go index e6f310f..b62cb74 100644 --- a/cmd/frpc/sub/tcp.go +++ b/cmd/frpc/sub/tcp.go @@ -41,7 +41,7 @@ var tcpCmd = &cobra.Command{ Use: "tcp", Short: "Run frpc with a single tcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/tcpmux.go b/cmd/frpc/sub/tcpmux.go index 065bf9c..6f46cf7 100644 --- a/cmd/frpc/sub/tcpmux.go +++ b/cmd/frpc/sub/tcpmux.go @@ -44,7 +44,7 @@ var tcpMuxCmd = &cobra.Command{ Use: "tcpmux", Short: "Run frpc with a single tcpmux proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/udp.go b/cmd/frpc/sub/udp.go index c521996..7f6dd3f 100644 --- a/cmd/frpc/sub/udp.go +++ b/cmd/frpc/sub/udp.go @@ -41,7 +41,7 @@ var udpCmd = &cobra.Command{ Use: "udp", Short: "Run frpc with a single udp proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frpc/sub/xtcp.go b/cmd/frpc/sub/xtcp.go index 6e66f95..1eb096f 100644 --- a/cmd/frpc/sub/xtcp.go +++ b/cmd/frpc/sub/xtcp.go @@ -45,7 +45,7 @@ var xtcpCmd = &cobra.Command{ Use: "xtcp", Short: "Run frpc with a single xtcp proxy", RunE: func(cmd *cobra.Command, args []string) error { - clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, "") + clientCfg, err := parseClientCommonCfg(CfgFileTypeCmd, nil) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/frps/root.go b/cmd/frps/root.go index 6dd268f..b76817b 100644 --- a/cmd/frps/root.go +++ b/cmd/frps/root.go @@ -106,7 +106,7 @@ var rootCmd = &cobra.Command{ var err error if cfgFile != "" { log.Info("frps uses config file: %s", cfgFile) - var content string + var content []byte content, err = config.GetRenderedConfFromFile(cfgFile) if err != nil { return err @@ -114,7 +114,7 @@ var rootCmd = &cobra.Command{ cfg, err = parseServerCommonCfg(CfgFileTypeIni, content) } else { log.Info("frps uses command line arguments for config") - cfg, err = parseServerCommonCfg(CfgFileTypeCmd, "") + cfg, err = parseServerCommonCfg(CfgFileTypeCmd, nil) } if err != nil { return err @@ -135,9 +135,9 @@ func Execute() { } } -func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommonConf, err error) { +func parseServerCommonCfg(fileType int, source []byte) (cfg config.ServerCommonConf, err error) { if fileType == CfgFileTypeIni { - cfg, err = parseServerCommonCfgFromIni(content) + cfg, err = config.UnmarshalServerConfFromIni(source) } else if fileType == CfgFileTypeCmd { cfg, err = parseServerCommonCfgFromCmd() } @@ -152,14 +152,6 @@ func parseServerCommonCfg(fileType int, content string) (cfg config.ServerCommon return } -func parseServerCommonCfgFromIni(content string) (config.ServerCommonConf, error) { - cfg, err := config.UnmarshalServerConfFromIni(content) - if err != nil { - return config.ServerCommonConf{}, err - } - return cfg, nil -} - func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) { cfg = config.GetDefaultServerConf() diff --git a/go.mod b/go.mod index 36ae6d6..dc9a9ec 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/prometheus/client_golang v1.4.1 github.com/rakyll/statik v0.1.1 github.com/rodaine/table v1.0.0 + github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/cobra v0.0.3 github.com/stretchr/testify v1.4.0 github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect @@ -34,6 +35,7 @@ require ( golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + gopkg.in/ini.v1 v1.62.0 gopkg.in/square/go-jose.v2 v2.4.1 // indirect k8s.io/apimachinery v0.18.3 -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index cd561f6..0cdf8d8 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= @@ -85,6 +87,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -154,6 +158,10 @@ github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY= github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -185,6 +193,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= @@ -221,6 +230,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= @@ -239,6 +249,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index a77123f..894da24 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -19,101 +19,58 @@ import ( "github.com/fatedier/frp/pkg/consts" "github.com/fatedier/frp/pkg/msg" - - "github.com/vaughan0/go-ini" ) -type baseConfig struct { +type BaseConfig struct { // AuthenticationMethod specifies what authentication method to use to // authenticate frpc with frps. If "token" is specified - token will be // read into login message. If "oidc" is specified - OIDC (Open ID Connect) // token will be issued using OIDC settings. By default, this value is "token". - AuthenticationMethod string `json:"authentication_method"` + AuthenticationMethod string `ini:"authentication_method" json:"authentication_method"` // AuthenticateHeartBeats specifies whether to include authentication token in // heartbeats sent to frps. By default, this value is false. - AuthenticateHeartBeats bool `json:"authenticate_heartbeats"` + AuthenticateHeartBeats bool `ini:"authenticate_heartbeats" json:"authenticate_heartbeats"` // AuthenticateNewWorkConns specifies whether to include authentication token in // new work connections sent to frps. By default, this value is false. - AuthenticateNewWorkConns bool `json:"authenticate_new_work_conns"` + AuthenticateNewWorkConns bool `ini:"authenticate_new_work_conns" json:"authenticate_new_work_conns"` } -func getDefaultBaseConf() baseConfig { - return baseConfig{ +func getDefaultBaseConf() BaseConfig { + return BaseConfig{ AuthenticationMethod: "token", AuthenticateHeartBeats: false, AuthenticateNewWorkConns: false, } } -func unmarshalBaseConfFromIni(conf ini.File) baseConfig { - var ( - tmpStr string - ok bool - ) - - cfg := getDefaultBaseConf() - - if tmpStr, ok = conf.Get("common", "authentication_method"); ok { - cfg.AuthenticationMethod = tmpStr - } - - if tmpStr, ok = conf.Get("common", "authenticate_heartbeats"); ok && tmpStr == "true" { - cfg.AuthenticateHeartBeats = true - } else { - cfg.AuthenticateHeartBeats = false - } - - if tmpStr, ok = conf.Get("common", "authenticate_new_work_conns"); ok && tmpStr == "true" { - cfg.AuthenticateNewWorkConns = true - } else { - cfg.AuthenticateNewWorkConns = false - } - - return cfg -} - type ClientConfig struct { - baseConfig - oidcClientConfig - tokenConfig + BaseConfig `ini:",extends"` + OidcClientConfig `ini:",extends"` + TokenConfig `ini:",extends"` } func GetDefaultClientConf() ClientConfig { return ClientConfig{ - baseConfig: getDefaultBaseConf(), - oidcClientConfig: getDefaultOidcClientConf(), - tokenConfig: getDefaultTokenConf(), + BaseConfig: getDefaultBaseConf(), + OidcClientConfig: getDefaultOidcClientConf(), + TokenConfig: getDefaultTokenConf(), } } -func UnmarshalClientConfFromIni(conf ini.File) (cfg ClientConfig) { - cfg.baseConfig = unmarshalBaseConfFromIni(conf) - cfg.oidcClientConfig = unmarshalOidcClientConfFromIni(conf) - cfg.tokenConfig = unmarshalTokenConfFromIni(conf) - return cfg -} - type ServerConfig struct { - baseConfig - oidcServerConfig - tokenConfig + BaseConfig `ini:",extends"` + OidcServerConfig `ini:",extends"` + TokenConfig `ini:",extends"` } func GetDefaultServerConf() ServerConfig { return ServerConfig{ - baseConfig: getDefaultBaseConf(), - oidcServerConfig: getDefaultOidcServerConf(), - tokenConfig: getDefaultTokenConf(), + BaseConfig: getDefaultBaseConf(), + OidcServerConfig: getDefaultOidcServerConf(), + TokenConfig: getDefaultTokenConf(), } } -func UnmarshalServerConfFromIni(conf ini.File) (cfg ServerConfig) { - cfg.baseConfig = unmarshalBaseConfFromIni(conf) - cfg.oidcServerConfig = unmarshalOidcServerConfFromIni(conf) - cfg.tokenConfig = unmarshalTokenConfFromIni(conf) - return cfg -} - type Setter interface { SetLogin(*msg.Login) error SetPing(*msg.Ping) error @@ -123,9 +80,9 @@ type Setter interface { func NewAuthSetter(cfg ClientConfig) (authProvider Setter) { switch cfg.AuthenticationMethod { case consts.TokenAuthMethod: - authProvider = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig) + authProvider = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig) case consts.OidcAuthMethod: - authProvider = NewOidcAuthSetter(cfg.baseConfig, cfg.oidcClientConfig) + authProvider = NewOidcAuthSetter(cfg.BaseConfig, cfg.OidcClientConfig) default: panic(fmt.Sprintf("wrong authentication method: '%s'", cfg.AuthenticationMethod)) } @@ -142,9 +99,9 @@ type Verifier interface { func NewAuthVerifier(cfg ServerConfig) (authVerifier Verifier) { switch cfg.AuthenticationMethod { case consts.TokenAuthMethod: - authVerifier = NewTokenAuth(cfg.baseConfig, cfg.tokenConfig) + authVerifier = NewTokenAuth(cfg.BaseConfig, cfg.TokenConfig) case consts.OidcAuthMethod: - authVerifier = NewOidcAuthVerifier(cfg.baseConfig, cfg.oidcServerConfig) + authVerifier = NewOidcAuthVerifier(cfg.BaseConfig, cfg.OidcServerConfig) } return authVerifier diff --git a/pkg/auth/oidc.go b/pkg/auth/oidc.go index a1e791a..981f758 100644 --- a/pkg/auth/oidc.go +++ b/pkg/auth/oidc.go @@ -21,30 +21,29 @@ import ( "github.com/fatedier/frp/pkg/msg" "github.com/coreos/go-oidc" - "github.com/vaughan0/go-ini" "golang.org/x/oauth2/clientcredentials" ) -type oidcClientConfig struct { +type OidcClientConfig struct { // OidcClientID specifies the client ID to use to get a token in OIDC // authentication if AuthenticationMethod == "oidc". By default, this value // is "". - OidcClientID string `json:"oidc_client_id"` + OidcClientID string `ini:"oidc_client_id" json:"oidc_client_id"` // OidcClientSecret specifies the client secret to use to get a token in OIDC // authentication if AuthenticationMethod == "oidc". By default, this value // is "". - OidcClientSecret string `json:"oidc_client_secret"` + OidcClientSecret string `ini:"oidc_client_secret" json:"oidc_client_secret"` // OidcAudience specifies the audience of the token in OIDC authentication //if AuthenticationMethod == "oidc". By default, this value is "". - OidcAudience string `json:"oidc_audience"` + OidcAudience string `ini:"oidc_audience" json:"oidc_audience"` // OidcTokenEndpointURL specifies the URL which implements OIDC Token Endpoint. // It will be used to get an OIDC token if AuthenticationMethod == "oidc". // By default, this value is "". - OidcTokenEndpointURL string `json:"oidc_token_endpoint_url"` + OidcTokenEndpointURL string `ini:"oidc_token_endpoint_url" json:"oidc_token_endpoint_url"` } -func getDefaultOidcClientConf() oidcClientConfig { - return oidcClientConfig{ +func getDefaultOidcClientConf() OidcClientConfig { + return OidcClientConfig{ OidcClientID: "", OidcClientSecret: "", OidcAudience: "", @@ -52,56 +51,29 @@ func getDefaultOidcClientConf() oidcClientConfig { } } -func unmarshalOidcClientConfFromIni(conf ini.File) oidcClientConfig { - var ( - tmpStr string - ok bool - ) - - cfg := getDefaultOidcClientConf() - - if tmpStr, ok = conf.Get("common", "oidc_client_id"); ok { - cfg.OidcClientID = tmpStr - } - - if tmpStr, ok = conf.Get("common", "oidc_client_secret"); ok { - cfg.OidcClientSecret = tmpStr - } - - if tmpStr, ok = conf.Get("common", "oidc_audience"); ok { - cfg.OidcAudience = tmpStr - } - - if tmpStr, ok = conf.Get("common", "oidc_token_endpoint_url"); ok { - cfg.OidcTokenEndpointURL = tmpStr - } - - return cfg -} - -type oidcServerConfig struct { +type OidcServerConfig struct { // OidcIssuer specifies the issuer to verify OIDC tokens with. This issuer // will be used to load public keys to verify signature and will be compared // with the issuer claim in the OIDC token. It will be used if // AuthenticationMethod == "oidc". By default, this value is "". - OidcIssuer string `json:"oidc_issuer"` + OidcIssuer string `ini:"oidc_issuer" json:"oidc_issuer"` // OidcAudience specifies the audience OIDC tokens should contain when validated. // If this value is empty, audience ("client ID") verification will be skipped. // It will be used when AuthenticationMethod == "oidc". By default, this // value is "". - OidcAudience string `json:"oidc_audience"` + OidcAudience string `ini:"oidc_audience" json:"oidc_audience"` // OidcSkipExpiryCheck specifies whether to skip checking if the OIDC token is // expired. It will be used when AuthenticationMethod == "oidc". By default, this // value is false. - OidcSkipExpiryCheck bool `json:"oidc_skip_expiry_check"` + OidcSkipExpiryCheck bool `ini:"oidc_skip_expiry_check" json:"oidc_skip_expiry_check"` // OidcSkipIssuerCheck specifies whether to skip checking if the OIDC token's // issuer claim matches the issuer specified in OidcIssuer. It will be used when // AuthenticationMethod == "oidc". By default, this value is false. - OidcSkipIssuerCheck bool `json:"oidc_skip_issuer_check"` + OidcSkipIssuerCheck bool `ini:"oidc_skip_issuer_check" json:"oidc_skip_issuer_check"` } -func getDefaultOidcServerConf() oidcServerConfig { - return oidcServerConfig{ +func getDefaultOidcServerConf() OidcServerConfig { + return OidcServerConfig{ OidcIssuer: "", OidcAudience: "", OidcSkipExpiryCheck: false, @@ -109,44 +81,13 @@ func getDefaultOidcServerConf() oidcServerConfig { } } -func unmarshalOidcServerConfFromIni(conf ini.File) oidcServerConfig { - var ( - tmpStr string - ok bool - ) - - cfg := getDefaultOidcServerConf() - - if tmpStr, ok = conf.Get("common", "oidc_issuer"); ok { - cfg.OidcIssuer = tmpStr - } - - if tmpStr, ok = conf.Get("common", "oidc_audience"); ok { - cfg.OidcAudience = tmpStr - } - - if tmpStr, ok = conf.Get("common", "oidc_skip_expiry_check"); ok && tmpStr == "true" { - cfg.OidcSkipExpiryCheck = true - } else { - cfg.OidcSkipExpiryCheck = false - } - - if tmpStr, ok = conf.Get("common", "oidc_skip_issuer_check"); ok && tmpStr == "true" { - cfg.OidcSkipIssuerCheck = true - } else { - cfg.OidcSkipIssuerCheck = false - } - - return cfg -} - type OidcAuthProvider struct { - baseConfig + BaseConfig tokenGenerator *clientcredentials.Config } -func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvider { +func NewOidcAuthSetter(baseCfg BaseConfig, cfg OidcClientConfig) *OidcAuthProvider { tokenGenerator := &clientcredentials.Config{ ClientID: cfg.OidcClientID, ClientSecret: cfg.OidcClientSecret, @@ -155,7 +96,7 @@ func NewOidcAuthSetter(baseCfg baseConfig, cfg oidcClientConfig) *OidcAuthProvid } return &OidcAuthProvider{ - baseConfig: baseCfg, + BaseConfig: baseCfg, tokenGenerator: tokenGenerator, } } @@ -192,13 +133,13 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e } type OidcAuthConsumer struct { - baseConfig + BaseConfig verifier *oidc.IDTokenVerifier subjectFromLogin string } -func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthConsumer { +func NewOidcAuthVerifier(baseCfg BaseConfig, cfg OidcServerConfig) *OidcAuthConsumer { provider, err := oidc.NewProvider(context.Background(), cfg.OidcIssuer) if err != nil { panic(err) @@ -210,7 +151,7 @@ func NewOidcAuthVerifier(baseCfg baseConfig, cfg oidcServerConfig) *OidcAuthCons SkipIssuerCheck: cfg.OidcSkipIssuerCheck, } return &OidcAuthConsumer{ - baseConfig: baseCfg, + BaseConfig: baseCfg, verifier: provider.Verifier(&verifierConf), } } diff --git a/pkg/auth/token.go b/pkg/auth/token.go index 02faf2f..1049174 100644 --- a/pkg/auth/token.go +++ b/pkg/auth/token.go @@ -20,47 +20,30 @@ import ( "github.com/fatedier/frp/pkg/msg" "github.com/fatedier/frp/pkg/util/util" - - "github.com/vaughan0/go-ini" ) -type tokenConfig struct { +type TokenConfig struct { // Token specifies the authorization token used to create keys to be sent // to the server. The server must have a matching token for authorization // to succeed. By default, this value is "". - Token string `json:"token"` + Token string `ini:"token" json:"token"` } -func getDefaultTokenConf() tokenConfig { - return tokenConfig{ +func getDefaultTokenConf() TokenConfig { + return TokenConfig{ Token: "", } } -func unmarshalTokenConfFromIni(conf ini.File) tokenConfig { - var ( - tmpStr string - ok bool - ) - - cfg := getDefaultTokenConf() - - if tmpStr, ok = conf.Get("common", "token"); ok { - cfg.Token = tmpStr - } - - return cfg -} - type TokenAuthSetterVerifier struct { - baseConfig + BaseConfig token string } -func NewTokenAuth(baseCfg baseConfig, cfg tokenConfig) *TokenAuthSetterVerifier { +func NewTokenAuth(baseCfg BaseConfig, cfg TokenConfig) *TokenAuthSetterVerifier { return &TokenAuthSetterVerifier{ - baseConfig: baseCfg, + BaseConfig: baseCfg, token: cfg.Token, } } diff --git a/pkg/config/README.md b/pkg/config/README.md new file mode 100644 index 0000000..d74f756 --- /dev/null +++ b/pkg/config/README.md @@ -0,0 +1,12 @@ +So far, there is no mature Go project that does well in parsing `*.ini` files. + +By comparison, we have selected an open source project: `https://github.com/go-ini/ini`. + +This library helped us solve most of the key-value matching, but there are still some problems, such as not supporting parsing `map`. + +We add our own logic on the basis of this library. In the current situationwhich, we need to complete the entire `Unmarshal` in two steps: + +* Step#1, use `go-ini` to complete the basic parameter matching; +* Step#2, parse our custom parameters to realize parsing special structure, like `map`, `array`. + +Some of the keywords in `tag`(like inline, extends, etc.) may be different from standard libraries such as `json` and `protobuf` in Go. For details, please refer to the library documentation: https://ini.unknwon.io/docs/intro. \ No newline at end of file diff --git a/pkg/config/client_common.go b/pkg/config/client.go similarity index 52% rename from pkg/config/client_common.go rename to pkg/config/client.go index 97f200a..4db4620 100644 --- a/pkg/config/client_common.go +++ b/pkg/config/client.go @@ -1,4 +1,4 @@ -// Copyright 2016 fatedier, fatedier@gmail.com +// Copyright 2020 The frp Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,125 +17,128 @@ package config import ( "fmt" "os" - "strconv" "strings" "github.com/fatedier/frp/pkg/auth" + "github.com/fatedier/frp/pkg/util/util" - ini "github.com/vaughan0/go-ini" + "gopkg.in/ini.v1" ) // ClientCommonConf contains information for a client service. It is // recommended to use GetDefaultClientConf instead of creating this object // directly, so that all unspecified fields have reasonable default values. type ClientCommonConf struct { - auth.ClientConfig + auth.ClientConfig `ini:",extends" json:"inline"` + // ServerAddr specifies the address of the server to connect to. By // default, this value is "0.0.0.0". - ServerAddr string `json:"server_addr"` + ServerAddr string `ini:"server_addr" josn:"server_addr"` // ServerPort specifies the port to connect to the server on. By default, // this value is 7000. - ServerPort int `json:"server_port"` + ServerPort int `ini:"server_port" json:"server_port"` // HTTPProxy specifies a proxy address to connect to the server through. If // this value is "", the server will be connected to directly. By default, // this value is read from the "http_proxy" environment variable. - HTTPProxy string `json:"http_proxy"` + HTTPProxy string `ini:"http_proxy" json:"http_proxy"` // LogFile specifies a file where logs will be written to. This value will // only be used if LogWay is set appropriately. By default, this value is // "console". - LogFile string `json:"log_file"` + LogFile string `ini:"log_file" json:"log_file"` // LogWay specifies the way logging is managed. Valid values are "console" // or "file". If "console" is used, logs will be printed to stdout. If // "file" is used, logs will be printed to LogFile. By default, this value // is "console". - LogWay string `json:"log_way"` + LogWay string `ini:"log_way" json:"log_way"` // LogLevel specifies the minimum log level. Valid values are "trace", // "debug", "info", "warn", and "error". By default, this value is "info". - LogLevel string `json:"log_level"` + LogLevel string `ini:"log_level" json:"log_level"` // LogMaxDays specifies the maximum number of days to store log information // before deletion. This is only used if LogWay == "file". By default, this // value is 0. - LogMaxDays int64 `json:"log_max_days"` + LogMaxDays int64 `ini:"log_max_days" json:"log_max_days"` // DisableLogColor disables log colors when LogWay == "console" when set to // true. By default, this value is false. - DisableLogColor bool `json:"disable_log_color"` + DisableLogColor bool `ini:"disable_log_color" json:"disable_log_color"` // AdminAddr specifies the address that the admin server binds to. By // default, this value is "127.0.0.1". - AdminAddr string `json:"admin_addr"` + AdminAddr string `ini:"admin_addr" json:"admin_addr"` // AdminPort specifies the port for the admin server to listen on. If this // value is 0, the admin server will not be started. By default, this value // is 0. - AdminPort int `json:"admin_port"` + AdminPort int `ini:"admin_port" json:"admin_port"` // AdminUser specifies the username that the admin server will use for // login. By default, this value is "admin". - AdminUser string `json:"admin_user"` + AdminUser string `ini:"admin_user" json:"admin_user"` // AdminPwd specifies the password that the admin server will use for // login. By default, this value is "admin". - AdminPwd string `json:"admin_pwd"` + AdminPwd string `ini:"admin_pwd" json:"admin_pwd"` // AssetsDir specifies the local directory that the admin server will load // resources from. If this value is "", assets will be loaded from the // bundled executable using statik. By default, this value is "". - AssetsDir string `json:"assets_dir"` + AssetsDir string `ini:"assets_dir" json:"assets_dir"` // PoolCount specifies the number of connections the client will make to // the server in advance. By default, this value is 0. - PoolCount int `json:"pool_count"` + PoolCount int `ini:"pool_count" json:"pool_count"` // TCPMux toggles TCP stream multiplexing. This allows multiple requests // from a client to share a single TCP connection. If this value is true, // the server must have TCP multiplexing enabled as well. By default, this // value is true. - TCPMux bool `json:"tcp_mux"` + TCPMux bool `ini:"tcp_mux" json:"tcp_mux"` // User specifies a prefix for proxy names to distinguish them from other // clients. If this value is not "", proxy names will automatically be // changed to "{user}.{proxy_name}". By default, this value is "". - User string `json:"user"` + User string `ini:"user" json:"user"` // DNSServer specifies a DNS server address for FRPC to use. If this value // is "", the default DNS will be used. By default, this value is "". - DNSServer string `json:"dns_server"` + DNSServer string `ini:"dns_server" json:"dns_server"` // LoginFailExit controls whether or not the client should exit after a // failed login attempt. If false, the client will retry until a login // attempt succeeds. By default, this value is true. - LoginFailExit bool `json:"login_fail_exit"` + LoginFailExit bool `ini:"login_fail_exit" json:"login_fail_exit"` // Start specifies a set of enabled proxies by name. If this set is empty, // all supplied proxies are enabled. By default, this value is an empty // set. - Start map[string]struct{} `json:"start"` + Start []string `ini:"start" json:"start"` + //Start map[string]struct{} `json:"start"` // Protocol specifies the protocol to use when interacting with the server. // Valid values are "tcp", "kcp" and "websocket". By default, this value // is "tcp". - Protocol string `json:"protocol"` + Protocol string `ini:"protocol" json:"protocol"` // TLSEnable specifies whether or not TLS should be used when communicating // with the server. If "tls_cert_file" and "tls_key_file" are valid, // client will load the supplied tls configuration. - TLSEnable bool `json:"tls_enable"` + TLSEnable bool `ini:"tls_enable" json:"tls_enable"` // ClientTLSCertPath specifies the path of the cert file that client will // load. It only works when "tls_enable" is true and "tls_key_file" is valid. - TLSCertFile string `json:"tls_cert_file"` + TLSCertFile string `ini:"tls_cert_file" json:"tls_cert_file"` // ClientTLSKeyPath specifies the path of the secret key file that client // will load. It only works when "tls_enable" is true and "tls_cert_file" // are valid. - TLSKeyFile string `json:"tls_key_file"` + TLSKeyFile string `ini:"tls_key_file" json:"tls_key_file"` // TrustedCaFile specifies the path of the trusted ca file that will load. // It only works when "tls_enable" is valid and tls configuration of server // has been specified. - TLSTrustedCaFile string `json:"tls_trusted_ca_file"` + TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"` // HeartBeatInterval specifies at what interval heartbeats are sent to the // server, in seconds. It is not recommended to change this value. By // default, this value is 30. - HeartbeatInterval int64 `json:"heartbeat_interval"` + HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"` // HeartBeatTimeout specifies the maximum allowed heartbeat response delay // before the connection is terminated, in seconds. It is not recommended // to change this value. By default, this value is 90. - HeartbeatTimeout int64 `json:"heartbeat_timeout"` + HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"` // Client meta info - Metas map[string]string `json:"metas"` + Metas map[string]string `ini:"-" json:"metas"` // UDPPacketSize specifies the udp packet size // By default, this value is 1500 - UDPPacketSize int64 `json:"udp_packet_size"` + UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` } // GetDefaultClientConf returns a client configuration with default values. func GetDefaultClientConf() ClientCommonConf { return ClientCommonConf{ + ClientConfig: auth.GetDefaultClientConf(), ServerAddr: "0.0.0.0", ServerPort: 7000, HTTPProxy: os.Getenv("http_proxy"), @@ -154,7 +157,7 @@ func GetDefaultClientConf() ClientCommonConf { User: "", DNSServer: "", LoginFailExit: true, - Start: make(map[string]struct{}), + Start: make([]string, 0), Protocol: "tcp", TLSEnable: false, TLSCertFile: "", @@ -167,185 +170,13 @@ func GetDefaultClientConf() ClientCommonConf { } } -func UnmarshalClientConfFromIni(content string) (cfg ClientCommonConf, err error) { - cfg = GetDefaultClientConf() - - conf, err := ini.Load(strings.NewReader(content)) - if err != nil { - return ClientCommonConf{}, fmt.Errorf("parse ini conf file error: %v", err) - } - - cfg.ClientConfig = auth.UnmarshalClientConfFromIni(conf) - - var ( - tmpStr string - ok bool - v int64 - ) - if tmpStr, ok = conf.Get("common", "server_addr"); ok { - cfg.ServerAddr = tmpStr - } - - if tmpStr, ok = conf.Get("common", "server_port"); ok { - v, err = strconv.ParseInt(tmpStr, 10, 64) - if err != nil { - err = fmt.Errorf("Parse conf error: invalid server_port") - return - } - cfg.ServerPort = int(v) - } - - if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" { - cfg.DisableLogColor = true - } - - if tmpStr, ok = conf.Get("common", "http_proxy"); ok { - cfg.HTTPProxy = tmpStr - } - - if tmpStr, ok = conf.Get("common", "log_file"); ok { - cfg.LogFile = tmpStr - if cfg.LogFile == "console" { - cfg.LogWay = "console" - } else { - cfg.LogWay = "file" - } - } - - if tmpStr, ok = conf.Get("common", "log_level"); ok { - cfg.LogLevel = tmpStr - } - - if tmpStr, ok = conf.Get("common", "log_max_days"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { - cfg.LogMaxDays = v - } - } - - if tmpStr, ok = conf.Get("common", "admin_addr"); ok { - cfg.AdminAddr = tmpStr - } - - if tmpStr, ok = conf.Get("common", "admin_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { - cfg.AdminPort = int(v) - } else { - err = fmt.Errorf("Parse conf error: invalid admin_port") - return - } - } - - if tmpStr, ok = conf.Get("common", "admin_user"); ok { - cfg.AdminUser = tmpStr - } - - if tmpStr, ok = conf.Get("common", "admin_pwd"); ok { - cfg.AdminPwd = tmpStr - } - - if tmpStr, ok = conf.Get("common", "assets_dir"); ok { - cfg.AssetsDir = tmpStr - } - - if tmpStr, ok = conf.Get("common", "pool_count"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err == nil { - cfg.PoolCount = int(v) - } - } - - if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" { - cfg.TCPMux = false - } else { - cfg.TCPMux = true - } - - if tmpStr, ok = conf.Get("common", "user"); ok { - cfg.User = tmpStr - } - - if tmpStr, ok = conf.Get("common", "dns_server"); ok { - cfg.DNSServer = tmpStr - } - - if tmpStr, ok = conf.Get("common", "start"); ok { - proxyNames := strings.Split(tmpStr, ",") - for _, name := range proxyNames { - cfg.Start[strings.TrimSpace(name)] = struct{}{} - } - } - - if tmpStr, ok = conf.Get("common", "login_fail_exit"); ok && tmpStr == "false" { - cfg.LoginFailExit = false - } else { - cfg.LoginFailExit = true - } - - if tmpStr, ok = conf.Get("common", "protocol"); ok { - // Now it only support tcp and kcp and websocket. - if tmpStr != "tcp" && tmpStr != "kcp" && tmpStr != "websocket" { - err = fmt.Errorf("Parse conf error: invalid protocol") - return - } - cfg.Protocol = tmpStr - } - - if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" { - cfg.TLSEnable = true - } else { - cfg.TLSEnable = false - } - - if tmpStr, ok = conf.Get("common", "tls_cert_file"); ok { - cfg.TLSCertFile = tmpStr - } - - if tmpStr, ok := conf.Get("common", "tls_key_file"); ok { - cfg.TLSKeyFile = tmpStr - } - - if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok { - cfg.TLSTrustedCaFile = tmpStr - } - - if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout") - return - } - cfg.HeartbeatTimeout = v - } - - if tmpStr, ok = conf.Get("common", "heartbeat_interval"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") - return - } - cfg.HeartbeatInterval = v - } - for k, v := range conf.Section("common") { - if strings.HasPrefix(k, "meta_") { - cfg.Metas[strings.TrimPrefix(k, "meta_")] = v - } - } - if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid udp_packet_size") - return - } - cfg.UDPPacketSize = v - } - return -} - -func (cfg *ClientCommonConf) Check() (err error) { +func (cfg *ClientCommonConf) Check() error { if cfg.HeartbeatInterval <= 0 { - err = fmt.Errorf("Parse conf error: invalid heartbeat_interval") - return + return fmt.Errorf("Parse conf error: invalid heartbeat_interval") } if cfg.HeartbeatTimeout < cfg.HeartbeatInterval { - err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval") - return + return fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval") } if cfg.TLSEnable == false { @@ -361,5 +192,178 @@ func (cfg *ClientCommonConf) Check() (err error) { fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false") } } - return + + return nil +} + +// Supported sources including: string(file path), []byte, Reader interface. +func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) { + f, err := ini.LoadSources(ini.LoadOptions{ + Insensitive: false, + InsensitiveSections: false, + InsensitiveKeys: false, + IgnoreInlineComment: true, + AllowBooleanKeys: true, + }, source) + if err != nil { + return ClientCommonConf{}, err + } + + s, err := f.GetSection("common") + if err != nil { + return ClientCommonConf{}, fmt.Errorf("invalid configuration file, not found [common] section") + } + + common := GetDefaultClientConf() + err = s.MapTo(&common) + if err != nil { + return ClientCommonConf{}, err + } + + common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_") + + return common, nil +} + +// if len(startProxy) is 0, start all +// otherwise just start proxies in startProxy map +func LoadAllProxyConfsFromIni( + prefix string, + source interface{}, + start []string, +) (map[string]ProxyConf, map[string]VisitorConf, error) { + + f, err := ini.LoadSources(ini.LoadOptions{ + Insensitive: false, + InsensitiveSections: false, + InsensitiveKeys: false, + IgnoreInlineComment: true, + AllowBooleanKeys: true, + }, source) + if err != nil { + return nil, nil, err + } + + proxyConfs := make(map[string]ProxyConf) + visitorConfs := make(map[string]VisitorConf) + + if prefix != "" { + prefix += "." + } + + startProxy := make(map[string]struct{}) + for _, s := range start { + startProxy[s] = struct{}{} + } + + startAll := true + if len(startProxy) > 0 { + startAll = false + } + + // Build template sections from range section And append to ini.File. + rangeSections := make([]*ini.Section, 0) + for _, section := range f.Sections() { + + if !strings.HasPrefix(section.Name(), "range:") { + continue + } + + rangeSections = append(rangeSections, section) + } + + for _, section := range rangeSections { + err = renderRangeProxyTemplates(f, section) + if err != nil { + return nil, nil, fmt.Errorf("fail to render range-section[%s] with error: %v", section.Name(), err) + } + } + + for _, section := range f.Sections() { + name := section.Name() + + if name == ini.DefaultSection || name == "common" || strings.HasPrefix(name, "range:") { + continue + } + + _, shouldStart := startProxy[name] + if !startAll && !shouldStart { + continue + } + + roleType := section.Key("role").String() + if roleType == "" { + roleType = "server" + } + + switch roleType { + case "server": + newConf, newErr := NewProxyConfFromIni(prefix, name, section) + if newErr != nil { + return nil, nil, fmt.Errorf("fail to parse section[%s], err: %v", name, newErr) + } + proxyConfs[prefix+name] = newConf + case "visitor": + newConf, newErr := NewVisitorConfFromIni(prefix, name, section) + if newErr != nil { + return nil, nil, newErr + } + visitorConfs[prefix+name] = newConf + default: + return nil, nil, fmt.Errorf("section[%s] role should be 'server' or 'visitor'", name) + } + } + return proxyConfs, visitorConfs, nil +} + +func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error { + + // Validation + localPortStr := section.Key("local_port").String() + remotePortStr := section.Key("remote_port").String() + if localPortStr == "" || remotePortStr == "" { + return fmt.Errorf("local_port or remote_port is empty") + } + + localPorts, err := util.ParseRangeNumbers(localPortStr) + if err != nil { + return err + } + + remotePorts, err := util.ParseRangeNumbers(remotePortStr) + if err != nil { + return err + } + + if len(localPorts) != len(remotePorts) { + return fmt.Errorf("local ports number should be same with remote ports number") + } + + if len(localPorts) == 0 { + return fmt.Errorf("local_port and remote_port is necessary") + } + + // Templates + prefix := strings.TrimSpace(strings.TrimPrefix(section.Name(), "range:")) + + for i := range localPorts { + tmpname := fmt.Sprintf("%s_%d", prefix, i) + + tmpsection, err := f.NewSection(tmpname) + if err != nil { + return err + } + + copySection(section, tmpsection) + tmpsection.NewKey("local_port", fmt.Sprintf("%d", localPorts[i])) + tmpsection.NewKey("remote_port", fmt.Sprintf("%d", remotePorts[i])) + } + + return nil +} + +func copySection(source, target *ini.Section) { + for key, value := range source.KeysHash() { + target.NewKey(key, value) + } } diff --git a/pkg/config/client_test.go b/pkg/config/client_test.go new file mode 100644 index 0000000..9ad85e3 --- /dev/null +++ b/pkg/config/client_test.go @@ -0,0 +1,643 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "testing" + + "github.com/fatedier/frp/pkg/auth" + "github.com/fatedier/frp/pkg/consts" + + "github.com/stretchr/testify/assert" +) + +const ( + testUser = "test" +) + +var ( + testClientBytesWithFull = []byte(` + # [common] is integral section + [common] + server_addr = 0.0.0.9 + server_port = 7009 + http_proxy = http://user:passwd@192.168.1.128:8080 + log_file = ./frpc.log9 + log_way = file + log_level = info9 + log_max_days = 39 + disable_log_color = false + authenticate_heartbeats = false + authenticate_new_work_conns = false + token = 12345678 + oidc_client_id = client-id + oidc_client_secret = client-secret + oidc_audience = audience + oidc_token_endpoint_url = endpoint_url + admin_addr = 127.0.0.9 + admin_port = 7409 + admin_user = admin9 + admin_pwd = admin9 + assets_dir = ./static9 + pool_count = 59 + tcp_mux + user = your_name + login_fail_exit + protocol = tcp + tls_enable = true + tls_cert_file = client.crt + tls_key_file = client.key + tls_trusted_ca_file = ca.crt + dns_server = 8.8.8.9 + start = ssh,dns + heartbeat_interval = 39 + heartbeat_timeout = 99 + meta_var1 = 123 + meta_var2 = 234 + udp_packet_size = 1509 + + # all proxy + [ssh] + type = tcp + local_ip = 127.0.0.9 + local_port = 29 + bandwidth_limit = 19MB + use_encryption + use_compression + remote_port = 6009 + group = test_group + group_key = 123456 + health_check_type = tcp + health_check_timeout_s = 3 + health_check_max_failed = 3 + health_check_interval_s = 19 + meta_var1 = 123 + meta_var2 = 234 + + [ssh_random] + type = tcp + local_ip = 127.0.0.9 + local_port = 29 + remote_port = 9 + + [range:tcp_port] + type = tcp + local_ip = 127.0.0.9 + local_port = 6010-6011,6019 + remote_port = 6010-6011,6019 + use_encryption = false + use_compression = false + + [dns] + type = udp + local_ip = 114.114.114.114 + local_port = 59 + remote_port = 6009 + use_encryption + use_compression + + [range:udp_port] + type = udp + local_ip = 114.114.114.114 + local_port = 6000,6010-6011 + remote_port = 6000,6010-6011 + use_encryption + use_compression + + [web01] + type = http + local_ip = 127.0.0.9 + local_port = 89 + use_encryption + use_compression + http_user = admin + http_pwd = admin + subdomain = web01 + custom_domains = web02.yourdomain.com + locations = /,/pic + host_header_rewrite = example.com + header_X-From-Where = frp + health_check_type = http + health_check_url = /status + health_check_interval_s = 19 + health_check_max_failed = 3 + health_check_timeout_s = 3 + + [web02] + type = https + local_ip = 127.0.0.9 + local_port = 8009 + use_encryption + use_compression + subdomain = web01 + custom_domains = web02.yourdomain.com + proxy_protocol_version = v2 + + [secret_tcp] + type = stcp + sk = abcdefg + local_ip = 127.0.0.1 + local_port = 22 + use_encryption = false + use_compression = false + + [p2p_tcp] + type = xtcp + sk = abcdefg + local_ip = 127.0.0.1 + local_port = 22 + use_encryption = false + use_compression = false + + [tcpmuxhttpconnect] + type = tcpmux + multiplexer = httpconnect + local_ip = 127.0.0.1 + local_port = 10701 + custom_domains = tunnel1 + + [plugin_unix_domain_socket] + type = tcp + remote_port = 6003 + plugin = unix_domain_socket + plugin_unix_path = /var/run/docker.sock + + [plugin_http_proxy] + type = tcp + remote_port = 6004 + plugin = http_proxy + plugin_http_user = abc + plugin_http_passwd = abc + + [plugin_socks5] + type = tcp + remote_port = 6005 + plugin = socks5 + plugin_user = abc + plugin_passwd = abc + + [plugin_static_file] + type = tcp + remote_port = 6006 + plugin = static_file + plugin_local_path = /var/www/blog + plugin_strip_prefix = static + plugin_http_user = abc + plugin_http_passwd = abc + + [plugin_https2http] + type = https + custom_domains = test.yourdomain.com + plugin = https2http + plugin_local_addr = 127.0.0.1:80 + plugin_crt_path = ./server.crt + plugin_key_path = ./server.key + plugin_host_header_rewrite = 127.0.0.1 + plugin_header_X-From-Where = frp + + [plugin_http2https] + type = http + custom_domains = test.yourdomain.com + plugin = http2https + plugin_local_addr = 127.0.0.1:443 + plugin_host_header_rewrite = 127.0.0.1 + plugin_header_X-From-Where = frp + + # visitor + [secret_tcp_visitor] + role = visitor + type = stcp + server_name = secret_tcp + sk = abcdefg + bind_addr = 127.0.0.1 + bind_port = 9000 + use_encryption = false + use_compression = false + + [p2p_tcp_visitor] + role = visitor + type = xtcp + server_name = p2p_tcp + sk = abcdefg + bind_addr = 127.0.0.1 + bind_port = 9001 + use_encryption = false + use_compression = false + `) +) + +func Test_LoadClientCommonConf(t *testing.T) { + assert := assert.New(t) + + expected := ClientCommonConf{ + ClientConfig: auth.ClientConfig{ + BaseConfig: auth.BaseConfig{ + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + }, + TokenConfig: auth.TokenConfig{ + Token: "12345678", + }, + OidcClientConfig: auth.OidcClientConfig{ + OidcClientID: "client-id", + OidcClientSecret: "client-secret", + OidcAudience: "audience", + OidcTokenEndpointURL: "endpoint_url", + }, + }, + ServerAddr: "0.0.0.9", + ServerPort: 7009, + HTTPProxy: "http://user:passwd@192.168.1.128:8080", + LogFile: "./frpc.log9", + LogWay: "file", + LogLevel: "info9", + LogMaxDays: 39, + DisableLogColor: false, + AdminAddr: "127.0.0.9", + AdminPort: 7409, + AdminUser: "admin9", + AdminPwd: "admin9", + AssetsDir: "./static9", + PoolCount: 59, + TCPMux: true, + User: "your_name", + LoginFailExit: true, + Protocol: "tcp", + TLSEnable: true, + TLSCertFile: "client.crt", + TLSKeyFile: "client.key", + TLSTrustedCaFile: "ca.crt", + DNSServer: "8.8.8.9", + Start: []string{"ssh", "dns"}, + HeartbeatInterval: 39, + HeartbeatTimeout: 99, + Metas: map[string]string{ + "var1": "123", + "var2": "234", + }, + UDPPacketSize: 1509, + } + + common, err := UnmarshalClientConfFromIni(testClientBytesWithFull) + assert.NoError(err) + assert.Equal(expected, common) +} + +func Test_LoadClientBasicConf(t *testing.T) { + assert := assert.New(t) + + proxyExpected := map[string]ProxyConf{ + testUser + ".ssh": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".ssh", + ProxyType: consts.TCPProxy, + UseCompression: true, + UseEncryption: true, + Group: "test_group", + GroupKey: "123456", + BandwidthLimit: MustBandwidthQuantity("19MB"), + Metas: map[string]string{ + "var1": "123", + "var2": "234", + }, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 29, + }, + HealthCheckConf: HealthCheckConf{ + HealthCheckType: consts.TCPProxy, + HealthCheckTimeoutS: 3, + HealthCheckMaxFailed: 3, + HealthCheckIntervalS: 19, + HealthCheckAddr: "127.0.0.9:29", + }, + }, + RemotePort: 6009, + }, + testUser + ".ssh_random": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".ssh_random", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 29, + }, + }, + RemotePort: 9, + }, + testUser + ".tcp_port_0": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".tcp_port_0", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6010, + }, + }, + RemotePort: 6010, + }, + testUser + ".tcp_port_1": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".tcp_port_1", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6011, + }, + }, + RemotePort: 6011, + }, + testUser + ".tcp_port_2": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".tcp_port_2", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6019, + }, + }, + RemotePort: 6019, + }, + testUser + ".dns": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".dns", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 59, + }, + }, + RemotePort: 6009, + }, + testUser + ".udp_port_0": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".udp_port_0", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6000, + }, + }, + RemotePort: 6000, + }, + testUser + ".udp_port_1": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".udp_port_1", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6010, + }, + }, + RemotePort: 6010, + }, + testUser + ".udp_port_2": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".udp_port_2", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6011, + }, + }, + RemotePort: 6011, + }, + testUser + ".web01": &HTTPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".web01", + ProxyType: consts.HTTPProxy, + UseCompression: true, + UseEncryption: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 89, + }, + HealthCheckConf: HealthCheckConf{ + HealthCheckType: consts.HTTPProxy, + HealthCheckTimeoutS: 3, + HealthCheckMaxFailed: 3, + HealthCheckIntervalS: 19, + HealthCheckURL: "http://127.0.0.9:89/status", + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"web02.yourdomain.com"}, + SubDomain: "web01", + }, + Locations: []string{"/", "/pic"}, + HTTPUser: "admin", + HTTPPwd: "admin", + HostHeaderRewrite: "example.com", + Headers: map[string]string{ + "X-From-Where": "frp", + }, + }, + testUser + ".web02": &HTTPSProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".web02", + ProxyType: consts.HTTPSProxy, + UseCompression: true, + UseEncryption: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 8009, + }, + ProxyProtocolVersion: "v2", + }, + DomainConf: DomainConf{ + CustomDomains: []string{"web02.yourdomain.com"}, + SubDomain: "web01", + }, + }, + testUser + ".secret_tcp": &STCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".secret_tcp", + ProxyType: consts.STCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 22, + }, + }, + Role: "server", + Sk: "abcdefg", + }, + testUser + ".p2p_tcp": &XTCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".p2p_tcp", + ProxyType: consts.XTCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 22, + }, + }, + Role: "server", + Sk: "abcdefg", + }, + testUser + ".tcpmuxhttpconnect": &TCPMuxProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".tcpmuxhttpconnect", + ProxyType: consts.TCPMuxProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 10701, + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"tunnel1"}, + SubDomain: "", + }, + Multiplexer: "httpconnect", + }, + testUser + ".plugin_unix_domain_socket": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_unix_domain_socket", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "unix_domain_socket", + PluginParams: map[string]string{ + "plugin_unix_path": "/var/run/docker.sock", + }, + }, + }, + RemotePort: 6003, + }, + testUser + ".plugin_http_proxy": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_http_proxy", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "http_proxy", + PluginParams: map[string]string{ + "plugin_http_user": "abc", + "plugin_http_passwd": "abc", + }, + }, + }, + RemotePort: 6004, + }, + testUser + ".plugin_socks5": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_socks5", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "socks5", + PluginParams: map[string]string{ + "plugin_user": "abc", + "plugin_passwd": "abc", + }, + }, + }, + RemotePort: 6005, + }, + testUser + ".plugin_static_file": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_static_file", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "static_file", + PluginParams: map[string]string{ + "plugin_local_path": "/var/www/blog", + "plugin_strip_prefix": "static", + "plugin_http_user": "abc", + "plugin_http_passwd": "abc", + }, + }, + }, + RemotePort: 6006, + }, + testUser + ".plugin_https2http": &HTTPSProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_https2http", + ProxyType: consts.HTTPSProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "https2http", + PluginParams: map[string]string{ + "plugin_local_addr": "127.0.0.1:80", + "plugin_crt_path": "./server.crt", + "plugin_key_path": "./server.key", + "plugin_host_header_rewrite": "127.0.0.1", + "plugin_header_X-From-Where": "frp", + }, + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"test.yourdomain.com"}, + }, + }, + testUser + ".plugin_http2https": &HTTPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testUser + ".plugin_http2https", + ProxyType: consts.HTTPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + Plugin: "http2https", + PluginParams: map[string]string{ + "plugin_local_addr": "127.0.0.1:443", + "plugin_host_header_rewrite": "127.0.0.1", + "plugin_header_X-From-Where": "frp", + }, + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"test.yourdomain.com"}, + }, + }, + } + + visitorExpected := map[string]VisitorConf{ + testUser + ".secret_tcp_visitor": &STCPVisitorConf{ + BaseVisitorConf: BaseVisitorConf{ + ProxyName: testUser + ".secret_tcp_visitor", + ProxyType: consts.STCPProxy, + Role: "visitor", + Sk: "abcdefg", + ServerName: testVisitorPrefix + "secret_tcp", + BindAddr: "127.0.0.1", + BindPort: 9000, + }, + }, + testUser + ".p2p_tcp_visitor": &XTCPVisitorConf{ + BaseVisitorConf: BaseVisitorConf{ + ProxyName: testUser + ".p2p_tcp_visitor", + ProxyType: consts.XTCPProxy, + Role: "visitor", + Sk: "abcdefg", + ServerName: testProxyPrefix + "p2p_tcp", + BindAddr: "127.0.0.1", + BindPort: 9001, + }, + }, + } + + proxyActual, visitorActual, err := LoadAllProxyConfsFromIni(testUser, testClientBytesWithFull, nil) + assert.NoError(err) + assert.Equal(proxyExpected, proxyActual) + assert.Equal(visitorExpected, visitorActual) + +} diff --git a/pkg/config/proxy.go b/pkg/config/proxy.go index ad5b64b..96a4fac 100644 --- a/pkg/config/proxy.go +++ b/pkg/config/proxy.go @@ -17,34 +17,28 @@ package config import ( "fmt" "reflect" - "strconv" "strings" "github.com/fatedier/frp/pkg/consts" "github.com/fatedier/frp/pkg/msg" - "github.com/fatedier/frp/pkg/util/util" - ini "github.com/vaughan0/go-ini" + "gopkg.in/ini.v1" ) +// Proxy var ( - proxyConfTypeMap map[string]reflect.Type + proxyConfTypeMap = map[string]reflect.Type{ + consts.TCPProxy: reflect.TypeOf(TCPProxyConf{}), + consts.TCPMuxProxy: reflect.TypeOf(TCPMuxProxyConf{}), + consts.UDPProxy: reflect.TypeOf(UDPProxyConf{}), + consts.HTTPProxy: reflect.TypeOf(HTTPProxyConf{}), + consts.HTTPSProxy: reflect.TypeOf(HTTPSProxyConf{}), + consts.STCPProxy: reflect.TypeOf(STCPProxyConf{}), + consts.XTCPProxy: reflect.TypeOf(XTCPProxyConf{}), + consts.SUDPProxy: reflect.TypeOf(SUDPProxyConf{}), + } ) -func init() { - proxyConfTypeMap = make(map[string]reflect.Type) - proxyConfTypeMap[consts.TCPProxy] = reflect.TypeOf(TCPProxyConf{}) - proxyConfTypeMap[consts.TCPMuxProxy] = reflect.TypeOf(TCPMuxProxyConf{}) - proxyConfTypeMap[consts.UDPProxy] = reflect.TypeOf(UDPProxyConf{}) - proxyConfTypeMap[consts.HTTPProxy] = reflect.TypeOf(HTTPProxyConf{}) - proxyConfTypeMap[consts.HTTPSProxy] = reflect.TypeOf(HTTPSProxyConf{}) - proxyConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPProxyConf{}) - proxyConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPProxyConf{}) - proxyConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPProxyConf{}) -} - -// NewConfByType creates a empty ProxyConf object by proxyType. -// If proxyType isn't exist, return nil. func NewConfByType(proxyType string) ProxyConf { v, ok := proxyConfTypeMap[proxyType] if !ok { @@ -56,87 +50,273 @@ func NewConfByType(proxyType string) ProxyConf { type ProxyConf interface { GetBaseInfo() *BaseProxyConf - UnmarshalFromMsg(pMsg *msg.NewProxy) - UnmarshalFromIni(prefix string, name string, conf ini.Section) error - MarshalToMsg(pMsg *msg.NewProxy) + UnmarshalFromMsg(*msg.NewProxy) + UnmarshalFromIni(string, string, *ini.Section) error + MarshalToMsg(*msg.NewProxy) CheckForCli() error - CheckForSvr(serverCfg ServerCommonConf) error - Compare(conf ProxyConf) bool + CheckForSvr(ServerCommonConf) error + Compare(ProxyConf) bool } -func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (cfg ProxyConf, err error) { - if pMsg.ProxyType == "" { - pMsg.ProxyType = consts.TCPProxy - } +// LocalSvrConf configures what location the client will to, or what +// plugin will be used. +type LocalSvrConf struct { + // LocalIP specifies the IP address or host name to to. + LocalIP string `ini:"local_ip" json:"local_ip"` + // LocalPort specifies the port to to. + LocalPort int `ini:"local_port" json:"local_port"` - cfg = NewConfByType(pMsg.ProxyType) - if cfg == nil { - err = fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType) - return - } - cfg.UnmarshalFromMsg(pMsg) - err = cfg.CheckForSvr(serverCfg) - return + // Plugin specifies what plugin should be used for ng. If this value + // is set, the LocalIp and LocalPort values will be ignored. By default, + // this value is "". + Plugin string `ini:"plugin" json:"plugin"` + // PluginParams specify parameters to be passed to the plugin, if one is + // being used. By default, this value is an empty map. + PluginParams map[string]string `ini:"-"` } -func NewProxyConfFromIni(prefix string, name string, section ini.Section) (cfg ProxyConf, err error) { - proxyType := section["type"] - if proxyType == "" { - proxyType = consts.TCPProxy - section["type"] = consts.TCPProxy - } - cfg = NewConfByType(proxyType) - if cfg == nil { - err = fmt.Errorf("proxy [%s] type [%s] error", name, proxyType) - return - } - if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - if err = cfg.CheckForCli(); err != nil { - return - } - return +// HealthCheckConf configures health checking. This can be useful for load +// balancing purposes to detect and remove proxies to failing services. +type HealthCheckConf struct { + // HealthCheckType specifies what protocol to use for health checking. + // Valid values include "tcp", "http", and "". If this value is "", health + // checking will not be performed. By default, this value is "". + // + // If the type is "tcp", a connection will be attempted to the target + // server. If a connection cannot be established, the health check fails. + // + // If the type is "http", a GET request will be made to the endpoint + // specified by HealthCheckURL. If the response is not a 200, the health + // check fails. + HealthCheckType string `ini:"health_check_type" json:"health_check_type"` // tcp | http + // HealthCheckTimeoutS specifies the number of seconds to wait for a health + // check attempt to connect. If the timeout is reached, this counts as a + // health check failure. By default, this value is 3. + HealthCheckTimeoutS int `ini:"health_check_timeout_s" json:"health_check_timeout_s"` + // HealthCheckMaxFailed specifies the number of allowed failures before the + // is stopped. By default, this value is 1. + HealthCheckMaxFailed int `ini:"health_check_max_failed" json:"health_check_max_failed"` + // HealthCheckIntervalS specifies the time in seconds between health + // checks. By default, this value is 10. + HealthCheckIntervalS int `ini:"health_check_interval_s" json:"health_check_interval_s"` + // HealthCheckURL specifies the address to send health checks to if the + // health check type is "http". + HealthCheckURL string `ini:"health_check_url" json:"health_check_url"` + // HealthCheckAddr specifies the address to connect to if the health check + // type is "tcp". + HealthCheckAddr string `ini:"-"` } -// BaseProxyConf provides configuration info that is common to all proxy types. +// BaseProxyConf provides configuration info that is common to all types. type BaseProxyConf struct { - // ProxyName is the name of this proxy. - ProxyName string `json:"proxy_name"` - // ProxyType specifies the type of this proxy. Valid values include "tcp", + // ProxyName is the name of this + ProxyName string `ini:"name" json:"name"` + // ProxyType specifies the type of this Valid values include "tcp", // "udp", "http", "https", "stcp", and "xtcp". By default, this value is // "tcp". - ProxyType string `json:"proxy_type"` + ProxyType string `ini:"type" json:"type"` // UseEncryption controls whether or not communication with the server will // be encrypted. Encryption is done using the tokens supplied in the server // and client configuration. By default, this value is false. - UseEncryption bool `json:"use_encryption"` + UseEncryption bool `ini:"use_encryption" json:"use_encryption"` // UseCompression controls whether or not communication with the server // will be compressed. By default, this value is false. - UseCompression bool `json:"use_compression"` - // Group specifies which group the proxy is a part of. The server will use + UseCompression bool `ini:"use_compression" json:"use_compression"` + // Group specifies which group the is a part of. The server will use // this information to load balance proxies in the same group. If the value - // is "", this proxy will not be in a group. By default, this value is "". - Group string `json:"group"` + // is "", this will not be in a group. By default, this value is "". + Group string `ini:"group" json:"group"` // GroupKey specifies a group key, which should be the same among proxies // of the same group. By default, this value is "". - GroupKey string `json:"group_key"` + GroupKey string `ini:"group_key" json:"group_key"` // ProxyProtocolVersion specifies which protocol version to use. Valid // values include "v1", "v2", and "". If the value is "", a protocol // version will be automatically selected. By default, this value is "". - ProxyProtocolVersion string `json:"proxy_protocol_version"` + ProxyProtocolVersion string `ini:"proxy_protocol_version" json:"proxy_protocol_version"` - // BandwidthLimit limit the proxy bandwidth + // BandwidthLimit limit the bandwidth // 0 means no limit - BandwidthLimit BandwidthQuantity `json:"bandwidth_limit"` + BandwidthLimit BandwidthQuantity `ini:"bandwidth_limit" json:"bandwidth_limit"` // meta info for each proxy - Metas map[string]string `json:"metas"` + Metas map[string]string `ini:"-" json:"metas"` - LocalSvrConf - HealthCheckConf + // TODO: LocalSvrConf => LocalAppConf + LocalSvrConf `ini:",extends" json:"inline"` + HealthCheckConf `ini:",extends" json:"inline"` +} + +type DomainConf struct { + CustomDomains []string `ini:"custom_domains" json:"custom_domains"` + SubDomain string `ini:"subdomain" json:"subdomain"` +} + +// HTTP +type HTTPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + DomainConf `ini:",extends" json:"inline"` + + Locations []string `ini:"locations" json:"locations"` + HTTPUser string `ini:"http_user" json:"http_user"` + HTTPPwd string `ini:"http_pwd" json:"http_pwd"` + HostHeaderRewrite string `ini:"host_header_rewrite" json:"host_header_rewrite"` + Headers map[string]string `ini:"-" json:"headers"` +} + +// HTTPS +type HTTPSProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + DomainConf `ini:",extends" json:"inline"` +} + +// TCP +type TCPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + RemotePort int `ini:"remote_port" json:"remote_port"` +} + +// TCPMux +type TCPMuxProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + DomainConf `ini:",extends" json:"inline"` + + Multiplexer string `ini:"multiplexer"` +} + +// STCP +type STCPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + + Role string `ini:"role" json:"role"` + Sk string `ini:"sk" json:"sk"` +} + +// XTCP +type XTCPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + + Role string `ini:"role" json:"role"` + Sk string `ini:"sk" json:"sk"` +} + +// UDP +type UDPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + + RemotePort int `ini:"remote_port" json:"remote_port"` +} + +// SUDP +type SUDPProxyConf struct { + BaseProxyConf `ini:",extends" json:"inline"` + + Role string `ini:"role" json:"role"` + Sk string `ini:"sk" json:"sk"` +} + +// Proxy Conf Loader +// DefaultProxyConf creates a empty ProxyConf object by proxyType. +// If proxyType doesn't exist, return nil. +func DefaultProxyConf(proxyType string) ProxyConf { + var conf ProxyConf + switch proxyType { + case consts.TCPProxy: + conf = &TCPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + } + case consts.TCPMuxProxy: + conf = &TCPMuxProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + } + case consts.UDPProxy: + conf = &UDPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + } + case consts.HTTPProxy: + conf = &HTTPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + } + case consts.HTTPSProxy: + conf = &HTTPSProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + } + case consts.STCPProxy: + conf = &STCPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + Role: "server", + } + case consts.XTCPProxy: + conf = &XTCPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + Role: "server", + } + case consts.SUDPProxy: + conf = &SUDPProxyConf{ + BaseProxyConf: defaultBaseProxyConf(proxyType), + Role: "server", + } + default: + return nil + } + + return conf +} + +// Proxy loaded from ini +func NewProxyConfFromIni(prefix, name string, section *ini.Section) (ProxyConf, error) { + // section.Key: if key not exists, section will set it with default value. + proxyType := section.Key("type").String() + if proxyType == "" { + proxyType = consts.TCPProxy + } + + conf := DefaultProxyConf(proxyType) + if conf == nil { + return nil, fmt.Errorf("proxy [%s] type [%s] error", name, proxyType) + } + + if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { + return nil, err + } + + if err := conf.CheckForCli(); err != nil { + return nil, err + } + + return conf, nil +} + +// Proxy loaded from msg +func NewProxyConfFromMsg(pMsg *msg.NewProxy, serverCfg ServerCommonConf) (ProxyConf, error) { + if pMsg.ProxyType == "" { + pMsg.ProxyType = consts.TCPProxy + } + + conf := DefaultProxyConf(pMsg.ProxyType) + if conf == nil { + return nil, fmt.Errorf("proxy [%s] type [%s] error", pMsg.ProxyName, pMsg.ProxyType) + } + + conf.UnmarshalFromMsg(pMsg) + + err := conf.CheckForSvr(serverCfg) + if err != nil { + return nil, err + } + + return conf, nil +} + +// Base +func defaultBaseProxyConf(proxyType string) BaseProxyConf { + return BaseProxyConf{ + ProxyType: proxyType, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + }, + } } func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf { @@ -155,63 +335,41 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool { !reflect.DeepEqual(cfg.Metas, cmp.Metas) { return false } - if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) { + + if !reflect.DeepEqual(cfg.LocalSvrConf, cmp.LocalSvrConf) { return false } - if !cfg.HealthCheckConf.compare(&cmp.HealthCheckConf) { + if !reflect.DeepEqual(cfg.HealthCheckConf, cmp.HealthCheckConf) { return false } + return true } -func (cfg *BaseProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.ProxyName = pMsg.ProxyName - cfg.ProxyType = pMsg.ProxyType - cfg.UseEncryption = pMsg.UseEncryption - cfg.UseCompression = pMsg.UseCompression - cfg.Group = pMsg.Group - cfg.GroupKey = pMsg.GroupKey - cfg.Metas = pMsg.Metas -} - -func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) error { - var ( - tmpStr string - ok bool - err error - ) +// BaseProxyConf apply custom logic changes. +func (cfg *BaseProxyConf) decorate(prefix string, name string, section *ini.Section) error { + // proxy_name cfg.ProxyName = prefix + name - cfg.ProxyType = section["type"] - tmpStr, ok = section["use_encryption"] - if ok && tmpStr == "true" { - cfg.UseEncryption = true + // metas_xxx + cfg.Metas = GetMapWithoutPrefix(section.KeysHash(), "meta_") + + // bandwidth_limit + if bandwidth, err := section.GetKey("bandwidth_limit"); err == nil { + cfg.BandwidthLimit, err = NewBandwidthQuantity(bandwidth.String()) + if err != nil { + return err + } } - tmpStr, ok = section["use_compression"] - if ok && tmpStr == "true" { - cfg.UseCompression = true - } - - cfg.Group = section["group"] - cfg.GroupKey = section["group_key"] - cfg.ProxyProtocolVersion = section["proxy_protocol_version"] - - if cfg.BandwidthLimit, err = NewBandwidthQuantity(section["bandwidth_limit"]); err != nil { - return err - } - - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return err - } - - if err = cfg.HealthCheckConf.UnmarshalFromIni(prefix, name, section); err != nil { - return err - } + // plugin_xxx + cfg.LocalSvrConf.PluginParams = GetMapByPrefix(section.KeysHash(), "plugin_") + // custom logic code if cfg.HealthCheckType == "tcp" && cfg.Plugin == "" { cfg.HealthCheckAddr = cfg.LocalIP + fmt.Sprintf(":%d", cfg.LocalPort) } + if cfg.HealthCheckType == "http" && cfg.Plugin == "" && cfg.HealthCheckURL != "" { s := fmt.Sprintf("http://%s:%d", cfg.LocalIP, cfg.LocalPort) if !strings.HasPrefix(cfg.HealthCheckURL, "/") { @@ -220,16 +378,10 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i cfg.HealthCheckURL = s + cfg.HealthCheckURL } - cfg.Metas = make(map[string]string) - for k, v := range section { - if strings.HasPrefix(k, "meta_") { - cfg.Metas[strings.TrimPrefix(k, "meta_")] = v - } - } return nil } -func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { +func (cfg *BaseProxyConf) marshalToMsg(pMsg *msg.NewProxy) { pMsg.ProxyName = cfg.ProxyName pMsg.ProxyType = cfg.ProxyType pMsg.UseEncryption = cfg.UseEncryption @@ -239,6 +391,16 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { pMsg.Metas = cfg.Metas } +func (cfg *BaseProxyConf) unmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.ProxyName = pMsg.ProxyName + cfg.ProxyType = pMsg.ProxyType + cfg.UseEncryption = pMsg.UseEncryption + cfg.UseCompression = pMsg.UseCompression + cfg.Group = pMsg.Group + cfg.GroupKey = pMsg.GroupKey + cfg.Metas = pMsg.Metas +} + func (cfg *BaseProxyConf) checkForCli() (err error) { if cfg.ProxyProtocolVersion != "" { if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" { @@ -255,85 +417,11 @@ func (cfg *BaseProxyConf) checkForCli() (err error) { return nil } -// Bind info -type BindInfoConf struct { - RemotePort int `json:"remote_port"` -} - -func (cfg *BindInfoConf) compare(cmp *BindInfoConf) bool { - if cfg.RemotePort != cmp.RemotePort { - return false - } - return true -} - -func (cfg *BindInfoConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.RemotePort = pMsg.RemotePort -} - -func (cfg *BindInfoConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - var ( - tmpStr string - ok bool - v int64 - ) - if tmpStr, ok = section["remote_port"]; ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] remote_port error", name) - } - cfg.RemotePort = int(v) - } else { - return fmt.Errorf("Parse conf error: proxy [%s] remote_port not found", name) - } +func (cfg *BaseProxyConf) checkForSvr(conf ServerCommonConf) error { return nil } -func (cfg *BindInfoConf) MarshalToMsg(pMsg *msg.NewProxy) { - pMsg.RemotePort = cfg.RemotePort -} - -// Domain info -type DomainConf struct { - CustomDomains []string `json:"custom_domains"` - SubDomain string `json:"sub_domain"` -} - -func (cfg *DomainConf) compare(cmp *DomainConf) bool { - if strings.Join(cfg.CustomDomains, " ") != strings.Join(cmp.CustomDomains, " ") || - cfg.SubDomain != cmp.SubDomain { - return false - } - return true -} - -func (cfg *DomainConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.CustomDomains = pMsg.CustomDomains - cfg.SubDomain = pMsg.SubDomain -} - -func (cfg *DomainConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - var ( - tmpStr string - ok bool - ) - if tmpStr, ok = section["custom_domains"]; ok { - cfg.CustomDomains = strings.Split(tmpStr, ",") - for i, domain := range cfg.CustomDomains { - cfg.CustomDomains[i] = strings.ToLower(strings.TrimSpace(domain)) - } - } - - if tmpStr, ok = section["subdomain"]; ok { - cfg.SubDomain = tmpStr - } - return -} - -func (cfg *DomainConf) MarshalToMsg(pMsg *msg.NewProxy) { - pMsg.CustomDomains = cfg.CustomDomains - pMsg.SubDomain = cfg.SubDomain -} - +// DomainConf func (cfg *DomainConf) check() (err error) { if len(cfg.CustomDomains) == 0 && cfg.SubDomain == "" { err = fmt.Errorf("custom_domains and subdomain should set at least one of them") @@ -370,70 +458,10 @@ func (cfg *DomainConf) checkForSvr(serverCfg ServerCommonConf) (err error) { return fmt.Errorf("'.' and '*' is not supported in subdomain") } } - return -} - -// LocalSvrConf configures what location the client will proxy to, or what -// plugin will be used. -type LocalSvrConf struct { - // LocalIP specifies the IP address or host name to proxy to. - LocalIP string `json:"local_ip"` - // LocalPort specifies the port to proxy to. - LocalPort int `json:"local_port"` - - // Plugin specifies what plugin should be used for proxying. If this value - // is set, the LocalIp and LocalPort values will be ignored. By default, - // this value is "". - Plugin string `json:"plugin"` - // PluginParams specify parameters to be passed to the plugin, if one is - // being used. By default, this value is an empty map. - PluginParams map[string]string `json:"plugin_params"` -} - -func (cfg *LocalSvrConf) compare(cmp *LocalSvrConf) bool { - if cfg.LocalIP != cmp.LocalIP || - cfg.LocalPort != cmp.LocalPort { - return false - } - if cfg.Plugin != cmp.Plugin || - len(cfg.PluginParams) != len(cmp.PluginParams) { - return false - } - for k, v := range cfg.PluginParams { - value, ok := cmp.PluginParams[k] - if !ok || v != value { - return false - } - } - return true -} - -func (cfg *LocalSvrConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - cfg.Plugin = section["plugin"] - cfg.PluginParams = make(map[string]string) - if cfg.Plugin != "" { - // get params begin with "plugin_" - for k, v := range section { - if strings.HasPrefix(k, "plugin_") { - cfg.PluginParams[k] = v - } - } - } else { - if cfg.LocalIP = section["local_ip"]; cfg.LocalIP == "" { - cfg.LocalIP = "127.0.0.1" - } - - if tmpStr, ok := section["local_port"]; ok { - if cfg.LocalPort, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] local_port error", name) - } - } else { - return fmt.Errorf("Parse conf error: proxy [%s] local_port not found", name) - } - } - return + return nil } +// LocalSvrConf func (cfg *LocalSvrConf) checkForCli() (err error) { if cfg.Plugin == "" { if cfg.LocalIP == "" { @@ -448,73 +476,7 @@ func (cfg *LocalSvrConf) checkForCli() (err error) { return } -// HealthCheckConf configures health checking. This can be useful for load -// balancing purposes to detect and remove proxies to failing services. -type HealthCheckConf struct { - // HealthCheckType specifies what protocol to use for health checking. - // Valid values include "tcp", "http", and "". If this value is "", health - // checking will not be performed. By default, this value is "". - // - // If the type is "tcp", a connection will be attempted to the target - // server. If a connection cannot be established, the health check fails. - // - // If the type is "http", a GET request will be made to the endpoint - // specified by HealthCheckURL. If the response is not a 200, the health - // check fails. - HealthCheckType string `json:"health_check_type"` // tcp | http - // HealthCheckTimeoutS specifies the number of seconds to wait for a health - // check attempt to connect. If the timeout is reached, this counts as a - // health check failure. By default, this value is 3. - HealthCheckTimeoutS int `json:"health_check_timeout_s"` - // HealthCheckMaxFailed specifies the number of allowed failures before the - // proxy is stopped. By default, this value is 1. - HealthCheckMaxFailed int `json:"health_check_max_failed"` - // HealthCheckIntervalS specifies the time in seconds between health - // checks. By default, this value is 10. - HealthCheckIntervalS int `json:"health_check_interval_s"` - // HealthCheckURL specifies the address to send health checks to if the - // health check type is "http". - HealthCheckURL string `json:"health_check_url"` - // HealthCheckAddr specifies the address to connect to if the health check - // type is "tcp". - HealthCheckAddr string `json:"-"` -} - -func (cfg *HealthCheckConf) compare(cmp *HealthCheckConf) bool { - if cfg.HealthCheckType != cmp.HealthCheckType || - cfg.HealthCheckTimeoutS != cmp.HealthCheckTimeoutS || - cfg.HealthCheckMaxFailed != cmp.HealthCheckMaxFailed || - cfg.HealthCheckIntervalS != cmp.HealthCheckIntervalS || - cfg.HealthCheckURL != cmp.HealthCheckURL { - return false - } - return true -} - -func (cfg *HealthCheckConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - cfg.HealthCheckType = section["health_check_type"] - cfg.HealthCheckURL = section["health_check_url"] - - if tmpStr, ok := section["health_check_timeout_s"]; ok { - if cfg.HealthCheckTimeoutS, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] health_check_timeout_s error", name) - } - } - - if tmpStr, ok := section["health_check_max_failed"]; ok { - if cfg.HealthCheckMaxFailed, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] health_check_max_failed error", name) - } - } - - if tmpStr, ok := section["health_check_interval_s"]; ok { - if cfg.HealthCheckIntervalS, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] health_check_interval_s error", name) - } - } - return -} - +// HealthCheckConf func (cfg *HealthCheckConf) checkForCli() error { if cfg.HealthCheckType != "" && cfg.HealthCheckType != "tcp" && cfg.HealthCheckType != "http" { return fmt.Errorf("unsupport health check type") @@ -527,113 +489,144 @@ func (cfg *HealthCheckConf) checkForCli() error { return nil } -// TCP -type TCPProxyConf struct { - BaseProxyConf - BindInfoConf +func preUnmarshalFromIni(cfg ProxyConf, prefix string, name string, section *ini.Section) error { + err := section.MapTo(cfg) + if err != nil { + return err + } + + err = cfg.GetBaseInfo().decorate(prefix, name, section) + if err != nil { + return err + } + + return nil } +// TCP func (cfg *TCPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*TCPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { return false } + + // Add custom logic equal if exists. + if cfg.RemotePort != cmpConf.RemotePort { + return false + } + return true } func (cfg *TCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) - cfg.BindInfoConf.UnmarshalFromMsg(pMsg) + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists + cfg.RemotePort = pMsg.RemotePort } -func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return +func (cfg *TCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err } - if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return + + // Add custom logic unmarshal if exists + + return nil } func (cfg *TCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - cfg.BindInfoConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists + pMsg.RemotePort = cfg.RemotePort } func (cfg *TCPProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { - return err + return } + + // Add custom logic check if exists + return } -func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } - -// TCP Multiplexer -type TCPMuxProxyConf struct { - BaseProxyConf - DomainConf - - Multiplexer string `json:"multiplexer"` +func (cfg *TCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { + return nil } +// TCPMux func (cfg *TCPMuxProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*TCPMuxProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) || - cfg.Multiplexer != cmpConf.Multiplexer { + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { return false } + + // Add custom logic equal if exists. + if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) { + return false + } + + if cfg.Multiplexer != cmpConf.Multiplexer { + return false + } + return true } +func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + + // Add custom logic unmarshal if exists + + return nil +} + func (cfg *TCPMuxProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) - cfg.DomainConf.UnmarshalFromMsg(pMsg) + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists + cfg.CustomDomains = pMsg.CustomDomains + cfg.SubDomain = pMsg.SubDomain cfg.Multiplexer = pMsg.Multiplexer } -func (cfg *TCPMuxProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - - cfg.Multiplexer = section["multiplexer"] - if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer { - return fmt.Errorf("parse conf error: proxy [%s] incorrect multiplexer [%s]", name, cfg.Multiplexer) - } - return -} - func (cfg *TCPMuxProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - cfg.DomainConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists + pMsg.CustomDomains = cfg.CustomDomains + pMsg.SubDomain = cfg.SubDomain pMsg.Multiplexer = cfg.Multiplexer } func (cfg *TCPMuxProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { - return err + return } + + // Add custom logic check if exists if err = cfg.DomainConf.checkForCli(); err != nil { - return err + return } + if cfg.Multiplexer != consts.HTTPConnectTCPMultiplexer { return fmt.Errorf("parse conf error: incorrect multiplexer [%s]", cfg.Multiplexer) } + return } @@ -650,101 +643,113 @@ func (cfg *TCPMuxProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) return } + return } // UDP -type UDPProxyConf struct { - BaseProxyConf - BindInfoConf -} - func (cfg *UDPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*UDPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.BindInfoConf.compare(&cmpConf.BindInfoConf) { + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { return false } + + // Add custom logic equal if exists. + if cfg.RemotePort != cmpConf.RemotePort { + return false + } + return true } -func (cfg *UDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) - cfg.BindInfoConf.UnmarshalFromMsg(pMsg) +func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + + // Add custom logic unmarshal if exists + + return nil } -func (cfg *UDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - if err = cfg.BindInfoConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return +func (cfg *UDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists + cfg.RemotePort = pMsg.RemotePort } func (cfg *UDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - cfg.BindInfoConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists + pMsg.RemotePort = cfg.RemotePort } func (cfg *UDPProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } + + // Add custom logic check if exists + return } -func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { return nil } - -// HTTP -type HTTPProxyConf struct { - BaseProxyConf - DomainConf - - Locations []string `json:"locations"` - HTTPUser string `json:"http_user"` - HTTPPwd string `json:"http_pwd"` - HostHeaderRewrite string `json:"host_header_rewrite"` - Headers map[string]string `json:"headers"` +func (cfg *UDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { + return nil } +// HTTP func (cfg *HTTPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*HTTPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) || - strings.Join(cfg.Locations, " ") != strings.Join(cmpConf.Locations, " ") || - cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || - cfg.HTTPUser != cmpConf.HTTPUser || - cfg.HTTPPwd != cmpConf.HTTPPwd || - len(cfg.Headers) != len(cmpConf.Headers) { + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { return false } - for k, v := range cfg.Headers { - v2, ok := cmpConf.Headers[k] - if !ok { - return false - } - if v != v2 { - return false - } + // Add custom logic equal if exists. + if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) { + return false } + + if !reflect.DeepEqual(cfg.Locations, cmpConf.Locations) || + cfg.HTTPUser != cmpConf.HTTPUser || + cfg.HTTPPwd != cmpConf.HTTPPwd || + cfg.HostHeaderRewrite != cmpConf.HostHeaderRewrite || + !reflect.DeepEqual(cfg.Headers, cmpConf.Headers) { + return false + } + return true } -func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) - cfg.DomainConf.UnmarshalFromMsg(pMsg) +func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + // Add custom logic unmarshal if exists + cfg.Headers = GetMapWithoutPrefix(section.KeysHash(), "header_") + + return nil +} + +func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists + cfg.CustomDomains = pMsg.CustomDomains + cfg.SubDomain = pMsg.SubDomain cfg.Locations = pMsg.Locations cfg.HostHeaderRewrite = pMsg.HostHeaderRewrite cfg.HTTPUser = pMsg.HTTPUser @@ -752,41 +757,12 @@ func (cfg *HTTPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { cfg.Headers = pMsg.Headers } -func (cfg *HTTPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - - var ( - tmpStr string - ok bool - ) - if tmpStr, ok = section["locations"]; ok { - cfg.Locations = strings.Split(tmpStr, ",") - } else { - cfg.Locations = []string{""} - } - - cfg.HostHeaderRewrite = section["host_header_rewrite"] - cfg.HTTPUser = section["http_user"] - cfg.HTTPPwd = section["http_pwd"] - cfg.Headers = make(map[string]string) - - for k, v := range section { - if strings.HasPrefix(k, "header_") { - cfg.Headers[strings.TrimPrefix(k, "header_")] = v - } - } - return -} - func (cfg *HTTPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - cfg.DomainConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + // Add custom logic marshal if exists + pMsg.CustomDomains = cfg.CustomDomains + pMsg.SubDomain = cfg.SubDomain pMsg.Locations = cfg.Locations pMsg.HostHeaderRewrite = cfg.HostHeaderRewrite pMsg.HTTPUser = cfg.HTTPUser @@ -798,9 +774,12 @@ func (cfg *HTTPProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } + + // Add custom logic check if exists if err = cfg.DomainConf.checkForCli(); err != nil { return } + return } @@ -808,59 +787,71 @@ func (cfg *HTTPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { if serverCfg.VhostHTTPPort == 0 { return fmt.Errorf("type [http] not support when vhost_http_port is not set") } + if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil { err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) return } + return } // HTTPS -type HTTPSProxyConf struct { - BaseProxyConf - DomainConf -} - func (cfg *HTTPSProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*HTTPSProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.DomainConf.compare(&cmpConf.DomainConf) { + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { return false } + + // Add custom logic equal if exists. + if !reflect.DeepEqual(cfg.DomainConf, cmpConf.DomainConf) { + return false + } + return true } -func (cfg *HTTPSProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) - cfg.DomainConf.UnmarshalFromMsg(pMsg) +func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + + // Add custom logic unmarshal if exists + + return nil } -func (cfg *HTTPSProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - if err = cfg.DomainConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return +func (cfg *HTTPSProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists + cfg.CustomDomains = pMsg.CustomDomains + cfg.SubDomain = pMsg.SubDomain } func (cfg *HTTPSProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - cfg.DomainConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists + pMsg.CustomDomains = cfg.CustomDomains + pMsg.SubDomain = cfg.SubDomain } func (cfg *HTTPSProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } + + // Add custom logic check if exists if err = cfg.DomainConf.checkForCli(); err != nil { return } + return } @@ -868,127 +859,124 @@ func (cfg *HTTPSProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { if serverCfg.VhostHTTPSPort == 0 { return fmt.Errorf("type [https] not support when vhost_https_port is not set") } + if err = cfg.DomainConf.checkForSvr(serverCfg); err != nil { err = fmt.Errorf("proxy [%s] domain conf check error: %v", cfg.ProxyName, err) return } + return } // SUDP -type SUDPProxyConf struct { - BaseProxyConf - - Role string `json:"role"` - Sk string `json:"sk"` -} - func (cfg *SUDPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*SUDPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - cfg.Role != cmpConf.Role || + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { + return false + } + + // Add custom logic equal if exists. + if cfg.Role != cmpConf.Role || cfg.Sk != cmpConf.Sk { return false } + return true } -func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return +func (cfg *SUDPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err } - cfg.Role = section["role"] - if cfg.Role != "server" { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) - } + // Add custom logic unmarshal if exists - cfg.Sk = section["sk"] - - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return -} - -func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) - pMsg.Sk = cfg.Sk -} - -func (cfg *SUDPProxyConf) CheckForCli() (err error) { - if err = cfg.BaseProxyConf.checkForCli(); err != nil { - return - } - if cfg.Role != "server" { - err = fmt.Errorf("role should be 'server'") - return - } - return -} - -func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { - return + return nil } // Only for role server. func (cfg *SUDPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists cfg.Sk = pMsg.Sk } -// STCP -type STCPProxyConf struct { - BaseProxyConf +func (cfg *SUDPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { + cfg.BaseProxyConf.marshalToMsg(pMsg) - Role string `json:"role"` - Sk string `json:"sk"` + // Add custom logic marshal if exists + pMsg.Sk = cfg.Sk } +func (cfg *SUDPProxyConf) CheckForCli() (err error) { + if err := cfg.BaseProxyConf.checkForCli(); err != nil { + return err + } + + // Add custom logic check if exists + if cfg.Role != "server" { + return fmt.Errorf("role should be 'server'") + } + + return nil +} + +func (cfg *SUDPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { + return nil +} + +// STCP func (cfg *STCPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*STCPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - cfg.Role != cmpConf.Role || + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { + return false + } + + // Add custom logic equal if exists. + if cfg.Role != cmpConf.Role || cfg.Sk != cmpConf.Sk { return false } + return true } +func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + + // Add custom logic unmarshal if exists + if cfg.Role == "" { + cfg.Role = "server" + } + + return nil +} + // Only for role server. func (cfg *STCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists cfg.Sk = pMsg.Sk } -func (cfg *STCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - - cfg.Role = section["role"] - if cfg.Role != "server" { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) - } - - cfg.Sk = section["sk"] - - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return -} - func (cfg *STCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists pMsg.Sk = cfg.Sk } @@ -996,66 +984,65 @@ func (cfg *STCPProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } + + // Add custom logic check if exists if cfg.Role != "server" { - err = fmt.Errorf("role should be 'server'") - return + return fmt.Errorf("role should be 'server'") } + return } -func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { - return +func (cfg *STCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { + return nil } // XTCP -type XTCPProxyConf struct { - BaseProxyConf - - Role string `json:"role"` - Sk string `json:"sk"` -} - func (cfg *XTCPProxyConf) Compare(cmp ProxyConf) bool { cmpConf, ok := cmp.(*XTCPProxyConf) if !ok { return false } - if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) || - !cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) || - cfg.Role != cmpConf.Role || + if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) { + return false + } + + // Add custom logic equal if exists. + if cfg.Role != cmpConf.Role || cfg.Sk != cmpConf.Sk { return false } + return true } +func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) error { + err := preUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { + return err + } + + // Add custom logic unmarshal if exists + if cfg.Role == "" { + cfg.Role = "server" + } + + return nil +} + // Only for role server. func (cfg *XTCPProxyConf) UnmarshalFromMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.UnmarshalFromMsg(pMsg) + cfg.BaseProxyConf.unmarshalFromMsg(pMsg) + + // Add custom logic unmarshal if exists cfg.Sk = pMsg.Sk } -func (cfg *XTCPProxyConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseProxyConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - - cfg.Role = section["role"] - if cfg.Role != "server" { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) - } - - cfg.Sk = section["sk"] - - if err = cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil { - return - } - return -} - func (cfg *XTCPProxyConf) MarshalToMsg(pMsg *msg.NewProxy) { - cfg.BaseProxyConf.MarshalToMsg(pMsg) + cfg.BaseProxyConf.marshalToMsg(pMsg) + + // Add custom logic marshal if exists pMsg.Sk = cfg.Sk } @@ -1063,125 +1050,15 @@ func (cfg *XTCPProxyConf) CheckForCli() (err error) { if err = cfg.BaseProxyConf.checkForCli(); err != nil { return } + + // Add custom logic check if exists if cfg.Role != "server" { - err = fmt.Errorf("role should be 'server'") - return + return fmt.Errorf("role should be 'server'") } + return } -func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) (err error) { - return -} - -func ParseRangeSection(name string, section ini.Section) (sections map[string]ini.Section, err error) { - localPorts, errRet := util.ParseRangeNumbers(section["local_port"]) - if errRet != nil { - err = fmt.Errorf("Parse conf error: range section [%s] local_port invalid, %v", name, errRet) - return - } - - remotePorts, errRet := util.ParseRangeNumbers(section["remote_port"]) - if errRet != nil { - err = fmt.Errorf("Parse conf error: range section [%s] remote_port invalid, %v", name, errRet) - return - } - if len(localPorts) != len(remotePorts) { - err = fmt.Errorf("Parse conf error: range section [%s] local ports number should be same with remote ports number", name) - return - } - if len(localPorts) == 0 { - err = fmt.Errorf("Parse conf error: range section [%s] local_port and remote_port is necessary", name) - return - } - - sections = make(map[string]ini.Section) - for i, port := range localPorts { - subName := fmt.Sprintf("%s_%d", name, i) - subSection := copySection(section) - subSection["local_port"] = fmt.Sprintf("%d", port) - subSection["remote_port"] = fmt.Sprintf("%d", remotePorts[i]) - sections[subName] = subSection - } - return -} - -// if len(startProxy) is 0, start all -// otherwise just start proxies in startProxy map -func LoadAllConfFromIni(prefix string, content string, startProxy map[string]struct{}) ( - proxyConfs map[string]ProxyConf, visitorConfs map[string]VisitorConf, err error) { - - conf, errRet := ini.Load(strings.NewReader(content)) - if errRet != nil { - err = errRet - return - } - - if prefix != "" { - prefix += "." - } - - startAll := true - if len(startProxy) > 0 { - startAll = false - } - proxyConfs = make(map[string]ProxyConf) - visitorConfs = make(map[string]VisitorConf) - for name, section := range conf { - if name == "common" { - continue - } - - _, shouldStart := startProxy[name] - if !startAll && !shouldStart { - continue - } - - subSections := make(map[string]ini.Section) - - if strings.HasPrefix(name, "range:") { - // range section - rangePrefix := strings.TrimSpace(strings.TrimPrefix(name, "range:")) - subSections, err = ParseRangeSection(rangePrefix, section) - if err != nil { - return - } - } else { - subSections[name] = section - } - - for subName, subSection := range subSections { - if subSection["role"] == "" { - subSection["role"] = "server" - } - role := subSection["role"] - if role == "server" { - cfg, errRet := NewProxyConfFromIni(prefix, subName, subSection) - if errRet != nil { - err = errRet - return - } - proxyConfs[prefix+subName] = cfg - } else if role == "visitor" { - cfg, errRet := NewVisitorConfFromIni(prefix, subName, subSection) - if errRet != nil { - err = errRet - return - } - visitorConfs[prefix+subName] = cfg - } else { - err = fmt.Errorf("role should be 'server' or 'visitor'") - return - } - } - } - return -} - -func copySection(section ini.Section) (out ini.Section) { - out = make(ini.Section) - for k, v := range section { - out[k] = v - } - return +func (cfg *XTCPProxyConf) CheckForSvr(serverCfg ServerCommonConf) error { + return nil } diff --git a/pkg/config/proxy_test.go b/pkg/config/proxy_test.go new file mode 100644 index 0000000..68c87b9 --- /dev/null +++ b/pkg/config/proxy_test.go @@ -0,0 +1,461 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "testing" + + "github.com/fatedier/frp/pkg/consts" + "github.com/stretchr/testify/assert" + + "gopkg.in/ini.v1" +) + +var ( + testLoadOptions = ini.LoadOptions{ + Insensitive: false, + InsensitiveSections: false, + InsensitiveKeys: false, + IgnoreInlineComment: true, + AllowBooleanKeys: true, + } + + testProxyPrefix = "test." +) + +func Test_Proxy_Interface(t *testing.T) { + for name := range proxyConfTypeMap { + NewConfByType(name) + } +} + +func Test_Proxy_UnmarshalFromIni(t *testing.T) { + assert := assert.New(t) + + testcases := []struct { + sname string + source []byte + expected ProxyConf + }{ + + { + sname: "ssh", + source: []byte(` + [ssh] + # tcp | udp | http | https | stcp | xtcp, default is tcp + type = tcp + local_ip = 127.0.0.9 + local_port = 29 + bandwidth_limit = 19MB + use_encryption + use_compression + remote_port = 6009 + group = test_group + group_key = 123456 + health_check_type = tcp + health_check_timeout_s = 3 + health_check_max_failed = 3 + health_check_interval_s = 19 + meta_var1 = 123 + meta_var2 = 234`), + expected: &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "ssh", + ProxyType: consts.TCPProxy, + UseCompression: true, + UseEncryption: true, + Group: "test_group", + GroupKey: "123456", + BandwidthLimit: MustBandwidthQuantity("19MB"), + Metas: map[string]string{ + "var1": "123", + "var2": "234", + }, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 29, + }, + HealthCheckConf: HealthCheckConf{ + HealthCheckType: consts.TCPProxy, + HealthCheckTimeoutS: 3, + HealthCheckMaxFailed: 3, + HealthCheckIntervalS: 19, + HealthCheckAddr: "127.0.0.9:29", + }, + }, + RemotePort: 6009, + }, + }, + { + sname: "ssh_random", + source: []byte(` + [ssh_random] + type = tcp + local_ip = 127.0.0.9 + local_port = 29 + remote_port = 9 + `), + expected: &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "ssh_random", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 29, + }, + }, + RemotePort: 9, + }, + }, + { + sname: "dns", + source: []byte(` + [dns] + type = udp + local_ip = 114.114.114.114 + local_port = 59 + remote_port = 6009 + use_encryption + use_compression + `), + expected: &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "dns", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 59, + }, + }, + RemotePort: 6009, + }, + }, + { + sname: "web01", + source: []byte(` + [web01] + type = http + local_ip = 127.0.0.9 + local_port = 89 + use_encryption + use_compression + http_user = admin + http_pwd = admin + subdomain = web01 + custom_domains = web02.yourdomain.com + locations = /,/pic + host_header_rewrite = example.com + header_X-From-Where = frp + health_check_type = http + health_check_url = /status + health_check_interval_s = 19 + health_check_max_failed = 3 + health_check_timeout_s = 3 + `), + expected: &HTTPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "web01", + ProxyType: consts.HTTPProxy, + UseCompression: true, + UseEncryption: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 89, + }, + HealthCheckConf: HealthCheckConf{ + HealthCheckType: consts.HTTPProxy, + HealthCheckTimeoutS: 3, + HealthCheckMaxFailed: 3, + HealthCheckIntervalS: 19, + HealthCheckURL: "http://127.0.0.9:89/status", + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"web02.yourdomain.com"}, + SubDomain: "web01", + }, + Locations: []string{"/", "/pic"}, + HTTPUser: "admin", + HTTPPwd: "admin", + HostHeaderRewrite: "example.com", + Headers: map[string]string{ + "X-From-Where": "frp", + }, + }, + }, + { + sname: "web02", + source: []byte(` + [web02] + type = https + local_ip = 127.0.0.9 + local_port = 8009 + use_encryption + use_compression + subdomain = web01 + custom_domains = web02.yourdomain.com + proxy_protocol_version = v2 + `), + expected: &HTTPSProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "web02", + ProxyType: consts.HTTPSProxy, + UseCompression: true, + UseEncryption: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 8009, + }, + ProxyProtocolVersion: "v2", + }, + DomainConf: DomainConf{ + CustomDomains: []string{"web02.yourdomain.com"}, + SubDomain: "web01", + }, + }, + }, + { + sname: "secret_tcp", + source: []byte(` + [secret_tcp] + type = stcp + sk = abcdefg + local_ip = 127.0.0.1 + local_port = 22 + use_encryption = false + use_compression = false + `), + expected: &STCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "secret_tcp", + ProxyType: consts.STCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 22, + }, + }, + Role: "server", + Sk: "abcdefg", + }, + }, + { + sname: "p2p_tcp", + source: []byte(` + [p2p_tcp] + type = xtcp + sk = abcdefg + local_ip = 127.0.0.1 + local_port = 22 + use_encryption = false + use_compression = false + `), + expected: &XTCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "p2p_tcp", + ProxyType: consts.XTCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 22, + }, + }, + Role: "server", + Sk: "abcdefg", + }, + }, + { + sname: "tcpmuxhttpconnect", + source: []byte(` + [tcpmuxhttpconnect] + type = tcpmux + multiplexer = httpconnect + local_ip = 127.0.0.1 + local_port = 10701 + custom_domains = tunnel1 + `), + expected: &TCPMuxProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "tcpmuxhttpconnect", + ProxyType: consts.TCPMuxProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.1", + LocalPort: 10701, + }, + }, + DomainConf: DomainConf{ + CustomDomains: []string{"tunnel1"}, + SubDomain: "", + }, + Multiplexer: "httpconnect", + }, + }, + } + + for _, c := range testcases { + f, err := ini.LoadSources(testLoadOptions, c.source) + assert.NoError(err) + + proxyType := f.Section(c.sname).Key("type").String() + assert.NotEmpty(proxyType) + + actual := DefaultProxyConf(proxyType) + assert.NotNil(actual) + + err = actual.UnmarshalFromIni(testProxyPrefix, c.sname, f.Section(c.sname)) + assert.NoError(err) + assert.Equal(c.expected, actual) + } +} + +func Test_RangeProxy_UnmarshalFromIni(t *testing.T) { + assert := assert.New(t) + + testcases := []struct { + sname string + source []byte + expected map[string]ProxyConf + }{ + { + sname: "range:tcp_port", + source: []byte(` + [range:tcp_port] + type = tcp + local_ip = 127.0.0.9 + local_port = 6010-6011,6019 + remote_port = 6010-6011,6019 + use_encryption = false + use_compression = false + `), + expected: map[string]ProxyConf{ + "tcp_port_0": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "tcp_port_0", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6010, + }, + }, + RemotePort: 6010, + }, + "tcp_port_1": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "tcp_port_1", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6011, + }, + }, + RemotePort: 6011, + }, + "tcp_port_2": &TCPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "tcp_port_2", + ProxyType: consts.TCPProxy, + LocalSvrConf: LocalSvrConf{ + LocalIP: "127.0.0.9", + LocalPort: 6019, + }, + }, + RemotePort: 6019, + }, + }, + }, + { + sname: "range:udp_port", + source: []byte(` + [range:udp_port] + type = udp + local_ip = 114.114.114.114 + local_port = 6000,6010-6011 + remote_port = 6000,6010-6011 + use_encryption + use_compression + `), + expected: map[string]ProxyConf{ + "udp_port_0": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "udp_port_0", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6000, + }, + }, + RemotePort: 6000, + }, + "udp_port_1": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "udp_port_1", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6010, + }, + }, + RemotePort: 6010, + }, + "udp_port_2": &UDPProxyConf{ + BaseProxyConf: BaseProxyConf{ + ProxyName: testProxyPrefix + "udp_port_2", + ProxyType: consts.UDPProxy, + UseEncryption: true, + UseCompression: true, + LocalSvrConf: LocalSvrConf{ + LocalIP: "114.114.114.114", + LocalPort: 6011, + }, + }, + RemotePort: 6011, + }, + }, + }, + } + + for _, c := range testcases { + + f, err := ini.LoadSources(testLoadOptions, c.source) + assert.NoError(err) + + actual := make(map[string]ProxyConf) + s := f.Section(c.sname) + + err = renderRangeProxyTemplates(f, s) + assert.NoError(err) + + f.DeleteSection(ini.DefaultSection) + f.DeleteSection(c.sname) + + for _, section := range f.Sections() { + proxyType := section.Key("type").String() + newsname := section.Name() + + tmp := DefaultProxyConf(proxyType) + err = tmp.UnmarshalFromIni(testProxyPrefix, newsname, section) + assert.NoError(err) + + actual[newsname] = tmp + } + + assert.Equal(c.expected, actual) + } + +} diff --git a/pkg/config/server.go b/pkg/config/server.go new file mode 100644 index 0000000..10d5b33 --- /dev/null +++ b/pkg/config/server.go @@ -0,0 +1,284 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "fmt" + "strings" + + "github.com/fatedier/frp/pkg/auth" + plugin "github.com/fatedier/frp/pkg/plugin/server" + "github.com/fatedier/frp/pkg/util/util" + + "gopkg.in/ini.v1" +) + +// ServerCommonConf contains information for a server service. It is +// recommended to use GetDefaultServerConf instead of creating this object +// directly, so that all unspecified fields have reasonable default values. +type ServerCommonConf struct { + auth.ServerConfig `ini:",extends" json:"inline"` + + // BindAddr specifies the address that the server binds to. By default, + // this value is "0.0.0.0". + BindAddr string `ini:"bind_addr" json:"bind_addr"` + // BindPort specifies the port that the server listens on. By default, this + // value is 7000. + BindPort int `ini:"bind_port" json:"bind_port"` + // BindUDPPort specifies the UDP port that the server listens on. If this + // value is 0, the server will not listen for UDP connections. By default, + // this value is 0 + BindUDPPort int `ini:"bind_udp_port" json:"bind_udp_port"` + // KCPBindPort specifies the KCP port that the server listens on. If this + // value is 0, the server will not listen for KCP connections. By default, + // this value is 0. + KCPBindPort int `ini:"kcp_bind_port" json:"kcp_bind_port"` + // ProxyBindAddr specifies the address that the proxy binds to. This value + // may be the same as BindAddr. By default, this value is "0.0.0.0". + ProxyBindAddr string `ini:"proxy_bind_addr" json:"proxy_bind_addr"` + // VhostHTTPPort specifies the port that the server listens for HTTP Vhost + // requests. If this value is 0, the server will not listen for HTTP + // requests. By default, this value is 0. + VhostHTTPPort int `ini:"vhost_http_port" json:"vhost_http_port"` + // VhostHTTPSPort specifies the port that the server listens for HTTPS + // Vhost requests. If this value is 0, the server will not listen for HTTPS + // requests. By default, this value is 0. + VhostHTTPSPort int `ini:"vhost_https_port" json:"vhost_https_port"` + // TCPMuxHTTPConnectPort specifies the port that the server listens for TCP + // HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP + // requests on one single port. If it's not - it will listen on this value for + // HTTP CONNECT requests. By default, this value is 0. + TCPMuxHTTPConnectPort int `ini:"tcpmux_httpconnect_port" json:"tcpmux_httpconnect_port"` + // VhostHTTPTimeout specifies the response header timeout for the Vhost + // HTTP server, in seconds. By default, this value is 60. + VhostHTTPTimeout int64 `ini:"vhost_http_timeout" json:"vhost_http_timeout"` + // DashboardAddr specifies the address that the dashboard binds to. By + // default, this value is "0.0.0.0". + DashboardAddr string `ini:"dashboard_addr" json:"dashboard_addr"` + // DashboardPort specifies the port that the dashboard listens on. If this + // value is 0, the dashboard will not be started. By default, this value is + // 0. + DashboardPort int `ini:"dashboard_port" json:"dashboard_port"` + // DashboardUser specifies the username that the dashboard will use for + // login. By default, this value is "admin". + DashboardUser string `ini:"dashboard_user" json:"dashboard_user"` + // DashboardUser specifies the password that the dashboard will use for + // login. By default, this value is "admin". + DashboardPwd string `ini:"dashboard_pwd" json:"dashboard_pwd"` + // EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} + // in /metrics api. + EnablePrometheus bool `ini:"enable_prometheus" json:"enable_prometheus"` + // AssetsDir specifies the local directory that the dashboard will load + // resources from. If this value is "", assets will be loaded from the + // bundled executable using statik. By default, this value is "". + AssetsDir string `ini:"assets_dir" json:"assets_dir"` + // LogFile specifies a file where logs will be written to. This value will + // only be used if LogWay is set appropriately. By default, this value is + // "console". + LogFile string `ini:"log_file" json:"log_file"` + // LogWay specifies the way logging is managed. Valid values are "console" + // or "file". If "console" is used, logs will be printed to stdout. If + // "file" is used, logs will be printed to LogFile. By default, this value + // is "console". + LogWay string `ini:"log_way" json:"log_way"` + // LogLevel specifies the minimum log level. Valid values are "trace", + // "debug", "info", "warn", and "error". By default, this value is "info". + LogLevel string `ini:"log_level" json:"log_level"` + // LogMaxDays specifies the maximum number of days to store log information + // before deletion. This is only used if LogWay == "file". By default, this + // value is 0. + LogMaxDays int64 `ini:"log_max_days" json:"log_max_days"` + // DisableLogColor disables log colors when LogWay == "console" when set to + // true. By default, this value is false. + DisableLogColor bool `ini:"disable_log_color" json:"disable_log_color"` + // DetailedErrorsToClient defines whether to send the specific error (with + // debug info) to frpc. By default, this value is true. + DetailedErrorsToClient bool `ini:"detailed_errors_to_client" json:"detailed_errors_to_client"` + + // SubDomainHost specifies the domain that will be attached to sub-domains + // requested by the client when using Vhost proxying. For example, if this + // value is set to "frps.com" and the client requested the subdomain + // "test", the resulting URL would be "test.frps.com". By default, this + // value is "". + SubDomainHost string `ini:"subdomain_host" json:"subdomain_host"` + // TCPMux toggles TCP stream multiplexing. This allows multiple requests + // from a client to share a single TCP connection. By default, this value + // is true. + TCPMux bool `ini:"tcp_mux" json:"tcp_mux"` + // Custom404Page specifies a path to a custom 404 page to display. If this + // value is "", a default page will be displayed. By default, this value is + // "". + Custom404Page string `ini:"custom_404_page" json:"custom_404_page"` + + // AllowPorts specifies a set of ports that clients are able to proxy to. + // If the length of this value is 0, all ports are allowed. By default, + // this value is an empty set. + AllowPorts map[int]struct{} `ini:"-" json:"-"` + // MaxPoolCount specifies the maximum pool size for each proxy. By default, + // this value is 5. + MaxPoolCount int64 `ini:"max_pool_count" json:"max_pool_count"` + // MaxPortsPerClient specifies the maximum number of ports a single client + // may proxy to. If this value is 0, no limit will be applied. By default, + // this value is 0. + MaxPortsPerClient int64 `ini:"max_ports_per_client" json:"max_ports_per_client"` + // TLSOnly specifies whether to only accept TLS-encrypted connections. + // By default, the value is false. + TLSOnly bool `ini:"tls_only" json:"tls_only"` + // TLSCertFile specifies the path of the cert file that the server will + // load. If "tls_cert_file", "tls_key_file" are valid, the server will use this + // supplied tls configuration. Otherwise, the server will use the tls + // configuration generated by itself. + TLSCertFile string `ini:"tls_cert_file" json:"tls_cert_file"` + // TLSKeyFile specifies the path of the secret key that the server will + // load. If "tls_cert_file", "tls_key_file" are valid, the server will use this + // supplied tls configuration. Otherwise, the server will use the tls + // configuration generated by itself. + TLSKeyFile string `ini:"tls_key_file" json:"tls_key_file"` + // TLSTrustedCaFile specifies the paths of the client cert files that the + // server will load. It only works when "tls_only" is true. If + // "tls_trusted_ca_file" is valid, the server will verify each client's + // certificate. + TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"` + // HeartBeatTimeout specifies the maximum time to wait for a heartbeat + // before terminating the connection. It is not recommended to change this + // value. By default, this value is 90. + HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"` + // UserConnTimeout specifies the maximum time to wait for a work + // connection. By default, this value is 10. + UserConnTimeout int64 `ini:"user_conn_timeout" json:"user_conn_timeout"` + // HTTPPlugins specify the server plugins support HTTP protocol. + HTTPPlugins map[string]plugin.HTTPPluginOptions `ini:"-" json:"http_plugins"` + // UDPPacketSize specifies the UDP packet size + // By default, this value is 1500 + UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"` +} + +// GetDefaultServerConf returns a server configuration with reasonable +// defaults. +func GetDefaultServerConf() ServerCommonConf { + return ServerCommonConf{ + ServerConfig: auth.GetDefaultServerConf(), + BindAddr: "0.0.0.0", + BindPort: 7000, + BindUDPPort: 0, + KCPBindPort: 0, + ProxyBindAddr: "0.0.0.0", + VhostHTTPPort: 0, + VhostHTTPSPort: 0, + TCPMuxHTTPConnectPort: 0, + VhostHTTPTimeout: 60, + DashboardAddr: "0.0.0.0", + DashboardPort: 0, + DashboardUser: "admin", + DashboardPwd: "admin", + EnablePrometheus: false, + AssetsDir: "", + LogFile: "console", + LogWay: "console", + LogLevel: "info", + LogMaxDays: 3, + DisableLogColor: false, + DetailedErrorsToClient: true, + SubDomainHost: "", + TCPMux: true, + AllowPorts: make(map[int]struct{}), + MaxPoolCount: 5, + MaxPortsPerClient: 0, + TLSOnly: false, + TLSCertFile: "", + TLSKeyFile: "", + TLSTrustedCaFile: "", + HeartbeatTimeout: 90, + UserConnTimeout: 10, + Custom404Page: "", + HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), + UDPPacketSize: 1500, + } +} + +func (cfg *ServerCommonConf) Check() error { + return nil +} + +func UnmarshalServerConfFromIni(source interface{}) (ServerCommonConf, error) { + + f, err := ini.LoadSources(ini.LoadOptions{ + Insensitive: false, + InsensitiveSections: false, + InsensitiveKeys: false, + IgnoreInlineComment: true, + AllowBooleanKeys: true, + }, source) + if err != nil { + return ServerCommonConf{}, err + } + + s, err := f.GetSection("common") + if err != nil { + // TODO: add error info + return ServerCommonConf{}, err + } + + common := GetDefaultServerConf() + err = s.MapTo(&common) + if err != nil { + return ServerCommonConf{}, err + } + + // allow_ports + allowPortStr := s.Key("allow_ports").String() + if allowPortStr != "" { + allowPorts, err := util.ParseRangeNumbers(allowPortStr) + if err != nil { + return ServerCommonConf{}, fmt.Errorf("Parse conf error: allow_ports: %v", err) + } + for _, port := range allowPorts { + common.AllowPorts[int(port)] = struct{}{} + } + } + + // plugin.xxx + pluginOpts := make(map[string]plugin.HTTPPluginOptions) + for _, section := range f.Sections() { + name := section.Name() + if !strings.HasPrefix(name, "plugin.") { + continue + } + + opt, err := loadHTTPPluginOpt(section) + if err != nil { + return ServerCommonConf{}, err + } + + pluginOpts[opt.Name] = *opt + } + common.HTTPPlugins = pluginOpts + + return common, nil +} + +func loadHTTPPluginOpt(section *ini.Section) (*plugin.HTTPPluginOptions, error) { + name := strings.TrimSpace(strings.TrimPrefix(section.Name(), "plugin.")) + + opt := new(plugin.HTTPPluginOptions) + err := section.MapTo(opt) + if err != nil { + return nil, err + } + + opt.Name = name + + return opt, nil +} diff --git a/pkg/config/server_common.go b/pkg/config/server_common.go deleted file mode 100644 index e9be208..0000000 --- a/pkg/config/server_common.go +++ /dev/null @@ -1,482 +0,0 @@ -// Copyright 2016 fatedier, fatedier@gmail.com -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - "fmt" - "strconv" - "strings" - - "github.com/fatedier/frp/pkg/auth" - plugin "github.com/fatedier/frp/pkg/plugin/server" - "github.com/fatedier/frp/pkg/util/util" - - ini "github.com/vaughan0/go-ini" -) - -// ServerCommonConf contains information for a server service. It is -// recommended to use GetDefaultServerConf instead of creating this object -// directly, so that all unspecified fields have reasonable default values. -type ServerCommonConf struct { - auth.ServerConfig - // BindAddr specifies the address that the server binds to. By default, - // this value is "0.0.0.0". - BindAddr string `json:"bind_addr"` - // BindPort specifies the port that the server listens on. By default, this - // value is 7000. - BindPort int `json:"bind_port"` - // BindUDPPort specifies the UDP port that the server listens on. If this - // value is 0, the server will not listen for UDP connections. By default, - // this value is 0 - BindUDPPort int `json:"bind_udp_port"` - // KCPBindPort specifies the KCP port that the server listens on. If this - // value is 0, the server will not listen for KCP connections. By default, - // this value is 0. - KCPBindPort int `json:"kcp_bind_port"` - // ProxyBindAddr specifies the address that the proxy binds to. This value - // may be the same as BindAddr. By default, this value is "0.0.0.0". - ProxyBindAddr string `json:"proxy_bind_addr"` - // VhostHTTPPort specifies the port that the server listens for HTTP Vhost - // requests. If this value is 0, the server will not listen for HTTP - // requests. By default, this value is 0. - VhostHTTPPort int `json:"vhost_http_port"` - // VhostHTTPSPort specifies the port that the server listens for HTTPS - // Vhost requests. If this value is 0, the server will not listen for HTTPS - // requests. By default, this value is 0. - VhostHTTPSPort int `json:"vhost_https_port"` - // TCPMuxHTTPConnectPort specifies the port that the server listens for TCP - // HTTP CONNECT requests. If the value is 0, the server will not multiplex TCP - // requests on one single port. If it's not - it will listen on this value for - // HTTP CONNECT requests. By default, this value is 0. - TCPMuxHTTPConnectPort int `json:"tcpmux_httpconnect_port"` - // VhostHTTPTimeout specifies the response header timeout for the Vhost - // HTTP server, in seconds. By default, this value is 60. - VhostHTTPTimeout int64 `json:"vhost_http_timeout"` - // DashboardAddr specifies the address that the dashboard binds to. By - // default, this value is "0.0.0.0". - DashboardAddr string `json:"dashboard_addr"` - // DashboardPort specifies the port that the dashboard listens on. If this - // value is 0, the dashboard will not be started. By default, this value is - // 0. - DashboardPort int `json:"dashboard_port"` - // DashboardUser specifies the username that the dashboard will use for - // login. By default, this value is "admin". - DashboardUser string `json:"dashboard_user"` - // DashboardUser specifies the password that the dashboard will use for - // login. By default, this value is "admin". - DashboardPwd string `json:"dashboard_pwd"` - // EnablePrometheus will export prometheus metrics on {dashboard_addr}:{dashboard_port} - // in /metrics api. - EnablePrometheus bool `json:"enable_prometheus"` - // AssetsDir specifies the local directory that the dashboard will load - // resources from. If this value is "", assets will be loaded from the - // bundled executable using statik. By default, this value is "". - AssetsDir string `json:"assets_dir"` - // LogFile specifies a file where logs will be written to. This value will - // only be used if LogWay is set appropriately. By default, this value is - // "console". - LogFile string `json:"log_file"` - // LogWay specifies the way logging is managed. Valid values are "console" - // or "file". If "console" is used, logs will be printed to stdout. If - // "file" is used, logs will be printed to LogFile. By default, this value - // is "console". - LogWay string `json:"log_way"` - // LogLevel specifies the minimum log level. Valid values are "trace", - // "debug", "info", "warn", and "error". By default, this value is "info". - LogLevel string `json:"log_level"` - // LogMaxDays specifies the maximum number of days to store log information - // before deletion. This is only used if LogWay == "file". By default, this - // value is 0. - LogMaxDays int64 `json:"log_max_days"` - // DisableLogColor disables log colors when LogWay == "console" when set to - // true. By default, this value is false. - DisableLogColor bool `json:"disable_log_color"` - // DetailedErrorsToClient defines whether to send the specific error (with - // debug info) to frpc. By default, this value is true. - DetailedErrorsToClient bool `json:"detailed_errors_to_client"` - - // SubDomainHost specifies the domain that will be attached to sub-domains - // requested by the client when using Vhost proxying. For example, if this - // value is set to "frps.com" and the client requested the subdomain - // "test", the resulting URL would be "test.frps.com". By default, this - // value is "". - SubDomainHost string `json:"subdomain_host"` - // TCPMux toggles TCP stream multiplexing. This allows multiple requests - // from a client to share a single TCP connection. By default, this value - // is true. - TCPMux bool `json:"tcp_mux"` - // Custom404Page specifies a path to a custom 404 page to display. If this - // value is "", a default page will be displayed. By default, this value is - // "". - Custom404Page string `json:"custom_404_page"` - - // AllowPorts specifies a set of ports that clients are able to proxy to. - // If the length of this value is 0, all ports are allowed. By default, - // this value is an empty set. - AllowPorts map[int]struct{} - // MaxPoolCount specifies the maximum pool size for each proxy. By default, - // this value is 5. - MaxPoolCount int64 `json:"max_pool_count"` - // MaxPortsPerClient specifies the maximum number of ports a single client - // may proxy to. If this value is 0, no limit will be applied. By default, - // this value is 0. - MaxPortsPerClient int64 `json:"max_ports_per_client"` - // TLSOnly specifies whether to only accept TLS-encrypted connections. - // By default, the value is false. - TLSOnly bool `json:"tls_only"` - // TLSCertFile specifies the path of the cert file that the server will - // load. If "tls_cert_file", "tls_key_file" are valid, the server will use this - // supplied tls configuration. Otherwise, the server will use the tls - // configuration generated by itself. - TLSCertFile string `json:"tls_cert_file"` - // TLSKeyFile specifies the path of the secret key that the server will - // load. If "tls_cert_file", "tls_key_file" are valid, the server will use this - // supplied tls configuration. Otherwise, the server will use the tls - // configuration generated by itself. - TLSKeyFile string `json:"tls_key_file"` - // TLSTrustedCaFile specifies the paths of the client cert files that the - // server will load. It only works when "tls_only" is true. If - // "tls_trusted_ca_file" is valid, the server will verify each client's - // certificate. - TLSTrustedCaFile string `json:"tls_trusted_ca_file"` - // HeartBeatTimeout specifies the maximum time to wait for a heartbeat - // before terminating the connection. It is not recommended to change this - // value. By default, this value is 90. - HeartbeatTimeout int64 `json:"heartbeat_timeout"` - // UserConnTimeout specifies the maximum time to wait for a work - // connection. By default, this value is 10. - UserConnTimeout int64 `json:"user_conn_timeout"` - // HTTPPlugins specify the server plugins support HTTP protocol. - HTTPPlugins map[string]plugin.HTTPPluginOptions `json:"http_plugins"` - // UDPPacketSize specifies the UDP packet size - // By default, this value is 1500 - UDPPacketSize int64 `json:"udp_packet_size"` -} - -// GetDefaultServerConf returns a server configuration with reasonable -// defaults. -func GetDefaultServerConf() ServerCommonConf { - return ServerCommonConf{ - BindAddr: "0.0.0.0", - BindPort: 7000, - BindUDPPort: 0, - KCPBindPort: 0, - ProxyBindAddr: "0.0.0.0", - VhostHTTPPort: 0, - VhostHTTPSPort: 0, - TCPMuxHTTPConnectPort: 0, - VhostHTTPTimeout: 60, - DashboardAddr: "0.0.0.0", - DashboardPort: 0, - DashboardUser: "admin", - DashboardPwd: "admin", - EnablePrometheus: false, - AssetsDir: "", - LogFile: "console", - LogWay: "console", - LogLevel: "info", - LogMaxDays: 3, - DisableLogColor: false, - DetailedErrorsToClient: true, - SubDomainHost: "", - TCPMux: true, - AllowPorts: make(map[int]struct{}), - MaxPoolCount: 5, - MaxPortsPerClient: 0, - TLSOnly: false, - TLSCertFile: "", - TLSKeyFile: "", - TLSTrustedCaFile: "", - HeartbeatTimeout: 90, - UserConnTimeout: 10, - Custom404Page: "", - HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), - UDPPacketSize: 1500, - } -} - -// UnmarshalServerConfFromIni parses the contents of a server configuration ini -// file and returns the resulting server configuration. -func UnmarshalServerConfFromIni(content string) (cfg ServerCommonConf, err error) { - cfg = GetDefaultServerConf() - - conf, err := ini.Load(strings.NewReader(content)) - if err != nil { - err = fmt.Errorf("parse ini conf file error: %v", err) - return ServerCommonConf{}, err - } - - UnmarshalPluginsFromIni(conf, &cfg) - - cfg.ServerConfig = auth.UnmarshalServerConfFromIni(conf) - - var ( - tmpStr string - ok bool - v int64 - ) - if tmpStr, ok = conf.Get("common", "bind_addr"); ok { - cfg.BindAddr = tmpStr - } - - if tmpStr, ok = conf.Get("common", "bind_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid bind_port") - return - } - cfg.BindPort = int(v) - } - - if tmpStr, ok = conf.Get("common", "bind_udp_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid bind_udp_port") - return - } - cfg.BindUDPPort = int(v) - } - - if tmpStr, ok = conf.Get("common", "kcp_bind_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid kcp_bind_port") - return - } - cfg.KCPBindPort = int(v) - } - - if tmpStr, ok = conf.Get("common", "proxy_bind_addr"); ok { - cfg.ProxyBindAddr = tmpStr - } else { - cfg.ProxyBindAddr = cfg.BindAddr - } - - if tmpStr, ok = conf.Get("common", "vhost_http_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid vhost_http_port") - return - } - cfg.VhostHTTPPort = int(v) - } else { - cfg.VhostHTTPPort = 0 - } - - if tmpStr, ok = conf.Get("common", "vhost_https_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid vhost_https_port") - return - } - cfg.VhostHTTPSPort = int(v) - } else { - cfg.VhostHTTPSPort = 0 - } - - if tmpStr, ok = conf.Get("common", "tcpmux_httpconnect_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid tcpmux_httpconnect_port") - return - } - cfg.TCPMuxHTTPConnectPort = int(v) - } else { - cfg.TCPMuxHTTPConnectPort = 0 - } - - if tmpStr, ok = conf.Get("common", "vhost_http_timeout"); ok { - v, errRet := strconv.ParseInt(tmpStr, 10, 64) - if errRet != nil || v < 0 { - err = fmt.Errorf("Parse conf error: invalid vhost_http_timeout") - return - } - cfg.VhostHTTPTimeout = v - } - - if tmpStr, ok = conf.Get("common", "dashboard_addr"); ok { - cfg.DashboardAddr = tmpStr - } else { - cfg.DashboardAddr = cfg.BindAddr - } - - if tmpStr, ok = conf.Get("common", "dashboard_port"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid dashboard_port") - return - } - cfg.DashboardPort = int(v) - } else { - cfg.DashboardPort = 0 - } - - if tmpStr, ok = conf.Get("common", "dashboard_user"); ok { - cfg.DashboardUser = tmpStr - } - - if tmpStr, ok = conf.Get("common", "dashboard_pwd"); ok { - cfg.DashboardPwd = tmpStr - } - - if tmpStr, ok = conf.Get("common", "enable_prometheus"); ok && tmpStr == "true" { - cfg.EnablePrometheus = true - } - - if tmpStr, ok = conf.Get("common", "assets_dir"); ok { - cfg.AssetsDir = tmpStr - } - - if tmpStr, ok = conf.Get("common", "log_file"); ok { - cfg.LogFile = tmpStr - if cfg.LogFile == "console" { - cfg.LogWay = "console" - } else { - cfg.LogWay = "file" - } - } - - if tmpStr, ok = conf.Get("common", "log_level"); ok { - cfg.LogLevel = tmpStr - } - - if tmpStr, ok = conf.Get("common", "log_max_days"); ok { - v, err = strconv.ParseInt(tmpStr, 10, 64) - if err == nil { - cfg.LogMaxDays = v - } - } - - if tmpStr, ok = conf.Get("common", "disable_log_color"); ok && tmpStr == "true" { - cfg.DisableLogColor = true - } - - if tmpStr, ok = conf.Get("common", "detailed_errors_to_client"); ok && tmpStr == "false" { - cfg.DetailedErrorsToClient = false - } else { - cfg.DetailedErrorsToClient = true - } - - if allowPortsStr, ok := conf.Get("common", "allow_ports"); ok { - // e.g. 1000-2000,2001,2002,3000-4000 - ports, errRet := util.ParseRangeNumbers(allowPortsStr) - if errRet != nil { - err = fmt.Errorf("Parse conf error: allow_ports: %v", errRet) - return - } - - for _, port := range ports { - cfg.AllowPorts[int(port)] = struct{}{} - } - } - - if tmpStr, ok = conf.Get("common", "max_pool_count"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid max_pool_count") - return - } - - if v < 0 { - err = fmt.Errorf("Parse conf error: invalid max_pool_count") - return - } - cfg.MaxPoolCount = v - } - - if tmpStr, ok = conf.Get("common", "max_ports_per_client"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid max_ports_per_client") - return - } - - if v < 0 { - err = fmt.Errorf("Parse conf error: invalid max_ports_per_client") - return - } - cfg.MaxPortsPerClient = v - } - - if tmpStr, ok = conf.Get("common", "subdomain_host"); ok { - cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr)) - } - - if tmpStr, ok = conf.Get("common", "tcp_mux"); ok && tmpStr == "false" { - cfg.TCPMux = false - } else { - cfg.TCPMux = true - } - - if tmpStr, ok = conf.Get("common", "custom_404_page"); ok { - cfg.Custom404Page = tmpStr - } - - if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok { - v, errRet := strconv.ParseInt(tmpStr, 10, 64) - if errRet != nil { - err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect") - return - } - cfg.HeartbeatTimeout = v - } - - if tmpStr, ok = conf.Get("common", "tls_only"); ok && tmpStr == "true" { - cfg.TLSOnly = true - } else { - cfg.TLSOnly = false - } - - if tmpStr, ok = conf.Get("common", "udp_packet_size"); ok { - if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil { - err = fmt.Errorf("Parse conf error: invalid udp_packet_size") - return - } - cfg.UDPPacketSize = v - } - - if tmpStr, ok := conf.Get("common", "tls_cert_file"); ok { - cfg.TLSCertFile = tmpStr - } - - if tmpStr, ok := conf.Get("common", "tls_key_file"); ok { - cfg.TLSKeyFile = tmpStr - } - - if tmpStr, ok := conf.Get("common", "tls_trusted_ca_file"); ok { - cfg.TLSTrustedCaFile = tmpStr - cfg.TLSOnly = true - } - - return -} - -func UnmarshalPluginsFromIni(sections ini.File, cfg *ServerCommonConf) { - for name, section := range sections { - if strings.HasPrefix(name, "plugin.") { - name = strings.TrimSpace(strings.TrimPrefix(name, "plugin.")) - var tls_verify, err = strconv.ParseBool(section["tls_verify"]) - if err != nil { - tls_verify = true - } - options := plugin.HTTPPluginOptions{ - Name: name, - Addr: section["addr"], - Path: section["path"], - Ops: strings.Split(section["ops"], ","), - TLSVerify: tls_verify, - } - for i := range options.Ops { - options.Ops[i] = strings.TrimSpace(options.Ops[i]) - } - cfg.HTTPPlugins[name] = options - } - } -} - -func (cfg *ServerCommonConf) Check() error { - return nil -} diff --git a/pkg/config/server_test.go b/pkg/config/server_test.go new file mode 100644 index 0000000..f60c2b8 --- /dev/null +++ b/pkg/config/server_test.go @@ -0,0 +1,207 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "testing" + + "github.com/fatedier/frp/pkg/auth" + "github.com/fatedier/frp/pkg/plugin/server" + + "github.com/stretchr/testify/assert" +) + +func Test_LoadServerCommonConf(t *testing.T) { + assert := assert.New(t) + + testcases := []struct { + source []byte + expected ServerCommonConf + }{ + { + source: []byte(` + # [common] is integral section + [common] + bind_addr = 0.0.0.9 + bind_port = 7009 + bind_udp_port = 7008 + kcp_bind_port = 7007 + proxy_bind_addr = 127.0.0.9 + vhost_http_port = 89 + vhost_https_port = 449 + vhost_http_timeout = 69 + tcpmux_httpconnect_port = 1339 + dashboard_addr = 0.0.0.9 + dashboard_port = 7509 + dashboard_user = admin9 + dashboard_pwd = admin9 + enable_prometheus + assets_dir = ./static9 + log_file = ./frps.log9 + log_way = file + log_level = info9 + log_max_days = 39 + disable_log_color = false + detailed_errors_to_client + authentication_method = token + authenticate_heartbeats = false + authenticate_new_work_conns = false + token = 123456789 + oidc_issuer = test9 + oidc_audience = test9 + oidc_skip_expiry_check + oidc_skip_issuer_check + heartbeat_timeout = 99 + user_conn_timeout = 9 + allow_ports = 10-12,99 + max_pool_count = 59 + max_ports_per_client = 9 + tls_only = false + tls_cert_file = server.crt + tls_key_file = server.key + tls_trusted_ca_file = ca.crt + subdomain_host = frps.com + tcp_mux + udp_packet_size = 1509 + [plugin.user-manager] + addr = 127.0.0.1:9009 + path = /handler + ops = Login + [plugin.port-manager] + addr = 127.0.0.1:9009 + path = /handler + ops = NewProxy + tls_verify + `), + expected: ServerCommonConf{ + ServerConfig: auth.ServerConfig{ + BaseConfig: auth.BaseConfig{ + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + }, + TokenConfig: auth.TokenConfig{ + Token: "123456789", + }, + OidcServerConfig: auth.OidcServerConfig{ + OidcIssuer: "test9", + OidcAudience: "test9", + OidcSkipExpiryCheck: true, + OidcSkipIssuerCheck: true, + }, + }, + BindAddr: "0.0.0.9", + BindPort: 7009, + BindUDPPort: 7008, + KCPBindPort: 7007, + ProxyBindAddr: "127.0.0.9", + VhostHTTPPort: 89, + VhostHTTPSPort: 449, + VhostHTTPTimeout: 69, + TCPMuxHTTPConnectPort: 1339, + DashboardAddr: "0.0.0.9", + DashboardPort: 7509, + DashboardUser: "admin9", + DashboardPwd: "admin9", + EnablePrometheus: true, + AssetsDir: "./static9", + LogFile: "./frps.log9", + LogWay: "file", + LogLevel: "info9", + LogMaxDays: 39, + DisableLogColor: false, + DetailedErrorsToClient: true, + HeartbeatTimeout: 99, + UserConnTimeout: 9, + AllowPorts: map[int]struct{}{ + 10: struct{}{}, + 11: struct{}{}, + 12: struct{}{}, + 99: struct{}{}, + }, + MaxPoolCount: 59, + MaxPortsPerClient: 9, + TLSOnly: false, + TLSCertFile: "server.crt", + TLSKeyFile: "server.key", + TLSTrustedCaFile: "ca.crt", + SubDomainHost: "frps.com", + TCPMux: true, + UDPPacketSize: 1509, + + HTTPPlugins: map[string]plugin.HTTPPluginOptions{ + "user-manager": { + Name: "user-manager", + Addr: "127.0.0.1:9009", + Path: "/handler", + Ops: []string{"Login"}, + }, + "port-manager": { + Name: "port-manager", + Addr: "127.0.0.1:9009", + Path: "/handler", + Ops: []string{"NewProxy"}, + TLSVerify: true, + }, + }, + }, + }, + { + source: []byte(` + # [common] is integral section + [common] + bind_addr = 0.0.0.9 + bind_port = 7009 + bind_udp_port = 7008 + `), + expected: ServerCommonConf{ + ServerConfig: auth.ServerConfig{ + BaseConfig: auth.BaseConfig{ + AuthenticationMethod: "token", + AuthenticateHeartBeats: false, + AuthenticateNewWorkConns: false, + }, + }, + BindAddr: "0.0.0.9", + BindPort: 7009, + BindUDPPort: 7008, + ProxyBindAddr: "0.0.0.0", + VhostHTTPTimeout: 60, + DashboardAddr: "0.0.0.0", + DashboardUser: "admin", + DashboardPwd: "admin", + EnablePrometheus: false, + LogFile: "console", + LogWay: "console", + LogLevel: "info", + LogMaxDays: 3, + DetailedErrorsToClient: true, + TCPMux: true, + AllowPorts: make(map[int]struct{}), + MaxPoolCount: 5, + HeartbeatTimeout: 90, + UserConnTimeout: 10, + HTTPPlugins: make(map[string]plugin.HTTPPluginOptions), + UDPPacketSize: 1500, + }, + }, + } + + for _, c := range testcases { + actual, err := UnmarshalServerConfFromIni(c.source) + assert.NoError(err) + assert.Equal(c.expected, actual) + } +} diff --git a/pkg/config/types.go b/pkg/config/types.go index 87c240d..062cc74 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -41,6 +41,15 @@ func NewBandwidthQuantity(s string) (BandwidthQuantity, error) { return q, nil } +func MustBandwidthQuantity(s string) BandwidthQuantity { + q := BandwidthQuantity{} + err := q.UnmarshalString(s) + if err != nil { + panic(err) + } + return q +} + func (q *BandwidthQuantity) Equal(u *BandwidthQuantity) bool { if q == nil && u == nil { return true diff --git a/pkg/config/utils.go b/pkg/config/utils.go new file mode 100644 index 0000000..aef674d --- /dev/null +++ b/pkg/config/utils.go @@ -0,0 +1,51 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "strings" +) + +func GetMapWithoutPrefix(set map[string]string, prefix string) map[string]string { + m := make(map[string]string) + + for key, value := range set { + if strings.HasPrefix(key, prefix) { + m[strings.TrimPrefix(key, prefix)] = value + } + } + + if len(m) == 0 { + return nil + } + + return m +} + +func GetMapByPrefix(set map[string]string, prefix string) map[string]string { + m := make(map[string]string) + + for key, value := range set { + if strings.HasPrefix(key, prefix) { + m[key] = value + } + } + + if len(m) == 0 { + return nil + } + + return m +} diff --git a/pkg/config/value.go b/pkg/config/value.go index 3457024..a3114e6 100644 --- a/pkg/config/value.go +++ b/pkg/config/value.go @@ -1,3 +1,17 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package config import ( @@ -34,8 +48,8 @@ func GetValues() *Values { } } -func RenderContent(in string) (out string, err error) { - tmpl, errRet := template.New("frp").Parse(in) +func RenderContent(in []byte) (out []byte, err error) { + tmpl, errRet := template.New("frp").Parse(string(in)) if errRet != nil { err = errRet return @@ -47,18 +61,17 @@ func RenderContent(in string) (out string, err error) { if err != nil { return } - out = buffer.String() + out = buffer.Bytes() return } -func GetRenderedConfFromFile(path string) (out string, err error) { +func GetRenderedConfFromFile(path string) (out []byte, err error) { var b []byte b, err = ioutil.ReadFile(path) if err != nil { return } - content := string(b) - out, err = RenderContent(content) + out, err = RenderContent(b) return } diff --git a/pkg/config/visitor.go b/pkg/config/visitor.go index 39958ea..f3552b3 100644 --- a/pkg/config/visitor.go +++ b/pkg/config/visitor.go @@ -17,72 +17,91 @@ package config import ( "fmt" "reflect" - "strconv" "github.com/fatedier/frp/pkg/consts" - ini "github.com/vaughan0/go-ini" + "gopkg.in/ini.v1" ) + +// Visitor var ( - visitorConfTypeMap map[string]reflect.Type + visitorConfTypeMap = map[string]reflect.Type{ + consts.STCPProxy: reflect.TypeOf(STCPVisitorConf{}), + consts.XTCPProxy: reflect.TypeOf(XTCPVisitorConf{}), + consts.SUDPProxy: reflect.TypeOf(SUDPVisitorConf{}), + } ) -func init() { - visitorConfTypeMap = make(map[string]reflect.Type) - visitorConfTypeMap[consts.STCPProxy] = reflect.TypeOf(STCPVisitorConf{}) - visitorConfTypeMap[consts.XTCPProxy] = reflect.TypeOf(XTCPVisitorConf{}) - visitorConfTypeMap[consts.SUDPProxy] = reflect.TypeOf(SUDPVisitorConf{}) -} - type VisitorConf interface { GetBaseInfo() *BaseVisitorConf Compare(cmp VisitorConf) bool - UnmarshalFromIni(prefix string, name string, section ini.Section) error + UnmarshalFromIni(prefix string, name string, section *ini.Section) error Check() error } -func NewVisitorConfByType(cfgType string) VisitorConf { - v, ok := visitorConfTypeMap[cfgType] +type BaseVisitorConf struct { + ProxyName string `ini:"name" json:"name"` + ProxyType string `ini:"type" json:"type"` + UseEncryption bool `ini:"use_encryption" json:"use_encryption"` + UseCompression bool `ini:"use_compression" json:"use_compression"` + Role string `ini:"role" json:"role"` + Sk string `ini:"sk" json:"sk"` + ServerName string `ini:"server_name" json:"server_name"` + BindAddr string `ini:"bind_addr" json:"bind_addr"` + BindPort int `ini:"bind_port" json:"bind_port"` +} + +type SUDPVisitorConf struct { + BaseVisitorConf `ini:",extends" json:"inline"` +} + +type STCPVisitorConf struct { + BaseVisitorConf `ini:",extends" json:"inline"` +} + +type XTCPVisitorConf struct { + BaseVisitorConf `ini:",extends" json:"inline"` +} + + +// DefaultVisitorConf creates a empty VisitorConf object by visitorType. +// If visitorType doesn't exist, return nil. +func DefaultVisitorConf(visitorType string) VisitorConf { + v, ok := visitorConfTypeMap[visitorType] if !ok { return nil } - cfg := reflect.New(v).Interface().(VisitorConf) - return cfg + + return reflect.New(v).Interface().(VisitorConf) } -func NewVisitorConfFromIni(prefix string, name string, section ini.Section) (cfg VisitorConf, err error) { - cfgType := section["type"] - if cfgType == "" { - err = fmt.Errorf("visitor [%s] type shouldn't be empty", name) - return +// Visitor loaded from ini +func NewVisitorConfFromIni(prefix string, name string, section *ini.Section) (VisitorConf, error) { + // section.Key: if key not exists, section will set it with default value. + visitorType := section.Key("type").String() + + if visitorType == "" { + return nil, fmt.Errorf("visitor [%s] type shouldn't be empty", name) } - cfg = NewVisitorConfByType(cfgType) - if cfg == nil { - err = fmt.Errorf("visitor [%s] type [%s] error", name, cfgType) - return + + conf := DefaultVisitorConf(visitorType) + if conf == nil { + return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) } - if err = cfg.UnmarshalFromIni(prefix, name, section); err != nil { - return + + if err := conf.UnmarshalFromIni(prefix, name, section); err != nil { + return nil, fmt.Errorf("visitor [%s] type [%s] error", name, visitorType) } - if err = cfg.Check(); err != nil { - return + + if err := conf.Check(); err != nil { + return nil, err } - return -} - -type BaseVisitorConf struct { - ProxyName string `json:"proxy_name"` - ProxyType string `json:"proxy_type"` - UseEncryption bool `json:"use_encryption"` - UseCompression bool `json:"use_compression"` - Role string `json:"role"` - Sk string `json:"sk"` - ServerName string `json:"server_name"` - BindAddr string `json:"bind_addr"` - BindPort int `json:"bind_port"` + + return conf, nil } +// Base func (cfg *BaseVisitorConf) GetBaseInfo() *BaseVisitorConf { return cfg } @@ -118,45 +137,40 @@ func (cfg *BaseVisitorConf) check() (err error) { return } -func (cfg *BaseVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - var ( - tmpStr string - ok bool - ) +func (cfg *BaseVisitorConf) unmarshalFromIni(prefix string, name string, section *ini.Section) error { + + // Custom decoration after basic unmarshal: + // proxy name cfg.ProxyName = prefix + name - cfg.ProxyType = section["type"] - if tmpStr, ok = section["use_encryption"]; ok && tmpStr == "true" { - cfg.UseEncryption = true - } - if tmpStr, ok = section["use_compression"]; ok && tmpStr == "true" { - cfg.UseCompression = true - } + // server_name + cfg.ServerName = prefix + cfg.ServerName - cfg.Role = section["role"] - if cfg.Role != "visitor" { - return fmt.Errorf("Parse conf error: proxy [%s] incorrect role [%s]", name, cfg.Role) - } - cfg.Sk = section["sk"] - cfg.ServerName = prefix + section["server_name"] - if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" { + // bind_addr + if cfg.BindAddr == "" { cfg.BindAddr = "127.0.0.1" } - if tmpStr, ok = section["bind_port"]; ok { - if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port incorrect", name) - } - } else { - return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name) - } return nil } -type SUDPVisitorConf struct { - BaseVisitorConf +func preVisitorUnmarshalFromIni(cfg VisitorConf, prefix string, name string, section *ini.Section) error { + err := section.MapTo(cfg) + if err != nil { + return err + } + + err = cfg.GetBaseInfo().unmarshalFromIni(prefix, name, section) + if err != nil { + return err + } + + return nil } +// SUDP +var _ VisitorConf = &SUDPVisitorConf{} + func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool { cmpConf, ok := cmp.(*SUDPVisitorConf) if !ok { @@ -166,13 +180,20 @@ func (cfg *SUDPVisitorConf) Compare(cmp VisitorConf) bool { if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { return false } + + // Add custom login equal, if exists + return true } -func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { +func (cfg *SUDPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) { + err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { return } + + // Add custom logic unmarshal, if exists + return } @@ -180,12 +201,14 @@ func (cfg *SUDPVisitorConf) Check() (err error) { if err = cfg.BaseVisitorConf.check(); err != nil { return } + + // Add custom logic validate, if exists + return } -type STCPVisitorConf struct { - BaseVisitorConf -} +// STCP +var _ VisitorConf = &STCPVisitorConf{} func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool { cmpConf, ok := cmp.(*STCPVisitorConf) @@ -196,13 +219,20 @@ func (cfg *STCPVisitorConf) Compare(cmp VisitorConf) bool { if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { return false } + + // Add custom login equal, if exists + return true } -func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { +func (cfg *STCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) { + err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { return } + + // Add custom logic unmarshal, if exists + return } @@ -210,12 +240,14 @@ func (cfg *STCPVisitorConf) Check() (err error) { if err = cfg.BaseVisitorConf.check(); err != nil { return } + + // Add custom logic validate, if exists + return } -type XTCPVisitorConf struct { - BaseVisitorConf -} +// XTCP +var _ VisitorConf = &XTCPVisitorConf{} func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool { cmpConf, ok := cmp.(*XTCPVisitorConf) @@ -226,13 +258,20 @@ func (cfg *XTCPVisitorConf) Compare(cmp VisitorConf) bool { if !cfg.BaseVisitorConf.compare(&cmpConf.BaseVisitorConf) { return false } + + // Add custom login equal, if exists + return true } -func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section ini.Section) (err error) { - if err = cfg.BaseVisitorConf.UnmarshalFromIni(prefix, name, section); err != nil { +func (cfg *XTCPVisitorConf) UnmarshalFromIni(prefix string, name string, section *ini.Section) (err error) { + err = preVisitorUnmarshalFromIni(cfg, prefix, name, section) + if err != nil { return } + + // Add custom logic unmarshal, if exists + return } @@ -240,5 +279,8 @@ func (cfg *XTCPVisitorConf) Check() (err error) { if err = cfg.BaseVisitorConf.check(); err != nil { return } + + // Add custom logic validate, if exists + return } diff --git a/pkg/config/visitor_test.go b/pkg/config/visitor_test.go new file mode 100644 index 0000000..98b2f09 --- /dev/null +++ b/pkg/config/visitor_test.go @@ -0,0 +1,108 @@ +// Copyright 2020 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "testing" + + "github.com/fatedier/frp/pkg/consts" + + "gopkg.in/ini.v1" + "github.com/stretchr/testify/assert" +) + +const testVisitorPrefix = "test." + +func Test_Visitor_Interface(t *testing.T) { + for name := range visitorConfTypeMap { + DefaultVisitorConf(name) + } +} + +func Test_Visitor_UnmarshalFromIni(t *testing.T) { + assert := assert.New(t) + + testcases := []struct { + sname string + source []byte + expected VisitorConf + }{ + { + sname: "secret_tcp_visitor", + source: []byte(` + [secret_tcp_visitor] + role = visitor + type = stcp + server_name = secret_tcp + sk = abcdefg + bind_addr = 127.0.0.1 + bind_port = 9000 + use_encryption = false + use_compression = false + `), + expected: &STCPVisitorConf{ + BaseVisitorConf: BaseVisitorConf{ + ProxyName: testVisitorPrefix + "secret_tcp_visitor", + ProxyType: consts.STCPProxy, + Role: "visitor", + Sk: "abcdefg", + ServerName: testVisitorPrefix + "secret_tcp", + BindAddr: "127.0.0.1", + BindPort: 9000, + }, + }, + }, + { + sname: "p2p_tcp_visitor", + source: []byte(` + [p2p_tcp_visitor] + role = visitor + type = xtcp + server_name = p2p_tcp + sk = abcdefg + bind_addr = 127.0.0.1 + bind_port = 9001 + use_encryption = false + use_compression = false + `), + expected: &XTCPVisitorConf{ + BaseVisitorConf: BaseVisitorConf{ + ProxyName: testVisitorPrefix + "p2p_tcp_visitor", + ProxyType: consts.XTCPProxy, + Role: "visitor", + Sk: "abcdefg", + ServerName: testProxyPrefix + "p2p_tcp", + BindAddr: "127.0.0.1", + BindPort: 9001, + }, + }, + }, + } + + for _, c := range testcases { + f, err := ini.LoadSources(testLoadOptions, c.source) + assert.NoError(err) + + visitorType := f.Section(c.sname).Key("type").String() + assert.NotEmpty(visitorType) + + actual := DefaultVisitorConf(visitorType) + assert.NotNil(actual) + + err = actual.UnmarshalFromIni(testVisitorPrefix, c.sname, f.Section(c.sname)) + assert.NoError(err) + assert.Equal(c.expected, actual) + } +} diff --git a/pkg/plugin/server/http.go b/pkg/plugin/server/http.go index 696f861..f4d9c5c 100644 --- a/pkg/plugin/server/http.go +++ b/pkg/plugin/server/http.go @@ -28,11 +28,11 @@ import ( ) type HTTPPluginOptions struct { - Name string - Addr string - Path string - Ops []string - TLSVerify bool + Name string `ini:"name"` + Addr string `ini:"addr"` + Path string `ini:"path"` + Ops []string `ini:"ops"` + TLSVerify bool `ini:"tls_verify"` } type httpPlugin struct {