controller.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // Copyright 2025 The frp Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package api
  15. import (
  16. "cmp"
  17. "fmt"
  18. "net"
  19. "net/http"
  20. "os"
  21. "slices"
  22. "strconv"
  23. "time"
  24. "github.com/fatedier/frp/client/proxy"
  25. "github.com/fatedier/frp/pkg/config"
  26. v1 "github.com/fatedier/frp/pkg/config/v1"
  27. "github.com/fatedier/frp/pkg/config/v1/validation"
  28. "github.com/fatedier/frp/pkg/policy/security"
  29. httppkg "github.com/fatedier/frp/pkg/util/http"
  30. "github.com/fatedier/frp/pkg/util/log"
  31. )
  32. // Controller handles HTTP API requests for frpc.
  33. type Controller struct {
  34. // getProxyStatus returns the current proxy status.
  35. // Returns nil if the control connection is not established.
  36. getProxyStatus func() []*proxy.WorkingStatus
  37. // serverAddr is the frps server address for display.
  38. serverAddr string
  39. // configFilePath is the path to the configuration file.
  40. configFilePath string
  41. // unsafeFeatures is used for validation.
  42. unsafeFeatures *security.UnsafeFeatures
  43. // updateConfig updates proxy and visitor configurations.
  44. updateConfig func(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error
  45. // gracefulClose gracefully stops the service.
  46. gracefulClose func(d time.Duration)
  47. }
  48. // ControllerParams contains parameters for creating an APIController.
  49. type ControllerParams struct {
  50. GetProxyStatus func() []*proxy.WorkingStatus
  51. ServerAddr string
  52. ConfigFilePath string
  53. UnsafeFeatures *security.UnsafeFeatures
  54. UpdateConfig func(proxyCfgs []v1.ProxyConfigurer, visitorCfgs []v1.VisitorConfigurer) error
  55. GracefulClose func(d time.Duration)
  56. }
  57. // NewController creates a new Controller.
  58. func NewController(params ControllerParams) *Controller {
  59. return &Controller{
  60. getProxyStatus: params.GetProxyStatus,
  61. serverAddr: params.ServerAddr,
  62. configFilePath: params.ConfigFilePath,
  63. unsafeFeatures: params.UnsafeFeatures,
  64. updateConfig: params.UpdateConfig,
  65. gracefulClose: params.GracefulClose,
  66. }
  67. }
  68. // Reload handles GET /api/reload
  69. func (c *Controller) Reload(ctx *httppkg.Context) (any, error) {
  70. strictConfigMode := false
  71. strictStr := ctx.Query("strictConfig")
  72. if strictStr != "" {
  73. strictConfigMode, _ = strconv.ParseBool(strictStr)
  74. }
  75. cliCfg, proxyCfgs, visitorCfgs, _, err := config.LoadClientConfig(c.configFilePath, strictConfigMode)
  76. if err != nil {
  77. log.Warnf("reload frpc proxy config error: %s", err.Error())
  78. return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
  79. }
  80. if _, err := validation.ValidateAllClientConfig(cliCfg, proxyCfgs, visitorCfgs, c.unsafeFeatures); err != nil {
  81. log.Warnf("reload frpc proxy config error: %s", err.Error())
  82. return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
  83. }
  84. if err := c.updateConfig(proxyCfgs, visitorCfgs); err != nil {
  85. log.Warnf("reload frpc proxy config error: %s", err.Error())
  86. return nil, httppkg.NewError(http.StatusInternalServerError, err.Error())
  87. }
  88. log.Infof("success reload conf")
  89. return nil, nil
  90. }
  91. // Stop handles POST /api/stop
  92. func (c *Controller) Stop(ctx *httppkg.Context) (any, error) {
  93. go c.gracefulClose(100 * time.Millisecond)
  94. return nil, nil
  95. }
  96. // Status handles GET /api/status
  97. func (c *Controller) Status(ctx *httppkg.Context) (any, error) {
  98. res := make(StatusResp)
  99. ps := c.getProxyStatus()
  100. if ps == nil {
  101. return res, nil
  102. }
  103. for _, status := range ps {
  104. res[status.Type] = append(res[status.Type], c.buildProxyStatusResp(status))
  105. }
  106. for _, arrs := range res {
  107. if len(arrs) <= 1 {
  108. continue
  109. }
  110. slices.SortFunc(arrs, func(a, b ProxyStatusResp) int {
  111. return cmp.Compare(a.Name, b.Name)
  112. })
  113. }
  114. return res, nil
  115. }
  116. // GetConfig handles GET /api/config
  117. func (c *Controller) GetConfig(ctx *httppkg.Context) (any, error) {
  118. if c.configFilePath == "" {
  119. return nil, httppkg.NewError(http.StatusBadRequest, "frpc has no config file path")
  120. }
  121. content, err := os.ReadFile(c.configFilePath)
  122. if err != nil {
  123. log.Warnf("load frpc config file error: %s", err.Error())
  124. return nil, httppkg.NewError(http.StatusBadRequest, err.Error())
  125. }
  126. return string(content), nil
  127. }
  128. // PutConfig handles PUT /api/config
  129. func (c *Controller) PutConfig(ctx *httppkg.Context) (any, error) {
  130. body, err := ctx.Body()
  131. if err != nil {
  132. return nil, httppkg.NewError(http.StatusBadRequest, fmt.Sprintf("read request body error: %v", err))
  133. }
  134. if len(body) == 0 {
  135. return nil, httppkg.NewError(http.StatusBadRequest, "body can't be empty")
  136. }
  137. if err := os.WriteFile(c.configFilePath, body, 0o600); err != nil {
  138. return nil, httppkg.NewError(http.StatusInternalServerError, fmt.Sprintf("write content to frpc config file error: %v", err))
  139. }
  140. return nil, nil
  141. }
  142. // buildProxyStatusResp creates a ProxyStatusResp from proxy.WorkingStatus
  143. func (c *Controller) buildProxyStatusResp(status *proxy.WorkingStatus) ProxyStatusResp {
  144. psr := ProxyStatusResp{
  145. Name: status.Name,
  146. Type: status.Type,
  147. Status: status.Phase,
  148. Err: status.Err,
  149. }
  150. baseCfg := status.Cfg.GetBaseConfig()
  151. if baseCfg.LocalPort != 0 {
  152. psr.LocalAddr = net.JoinHostPort(baseCfg.LocalIP, strconv.Itoa(baseCfg.LocalPort))
  153. }
  154. psr.Plugin = baseCfg.Plugin.Type
  155. if status.Err == "" {
  156. psr.RemoteAddr = status.RemoteAddr
  157. if slices.Contains([]string{"tcp", "udp"}, status.Type) {
  158. psr.RemoteAddr = c.serverAddr + psr.RemoteAddr
  159. }
  160. }
  161. return psr
  162. }