dashboard_api.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. // Copyright 2017 fatedier, fatedier@gmail.com
  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 server
  15. import (
  16. "cmp"
  17. "encoding/json"
  18. "fmt"
  19. "net/http"
  20. "slices"
  21. "strings"
  22. "time"
  23. "github.com/gorilla/mux"
  24. "github.com/prometheus/client_golang/prometheus/promhttp"
  25. "github.com/fatedier/frp/pkg/config/types"
  26. v1 "github.com/fatedier/frp/pkg/config/v1"
  27. "github.com/fatedier/frp/pkg/metrics/mem"
  28. httppkg "github.com/fatedier/frp/pkg/util/http"
  29. "github.com/fatedier/frp/pkg/util/log"
  30. netpkg "github.com/fatedier/frp/pkg/util/net"
  31. "github.com/fatedier/frp/pkg/util/version"
  32. )
  33. type GeneralResponse struct {
  34. Code int
  35. Msg string
  36. }
  37. func (svr *Service) registerRouteHandlers(helper *httppkg.RouterRegisterHelper) {
  38. helper.Router.HandleFunc("/healthz", svr.healthz)
  39. subRouter := helper.Router.NewRoute().Subrouter()
  40. subRouter.Use(helper.AuthMiddleware.Middleware)
  41. // metrics
  42. if svr.cfg.EnablePrometheus {
  43. subRouter.Handle("/metrics", promhttp.Handler())
  44. }
  45. // apis
  46. subRouter.HandleFunc("/api/serverinfo", svr.apiServerInfo).Methods("GET")
  47. subRouter.HandleFunc("/api/proxy/{type}", svr.apiProxyByType).Methods("GET")
  48. subRouter.HandleFunc("/api/proxy/{type}/{name}", svr.apiProxyByTypeAndName).Methods("GET")
  49. subRouter.HandleFunc("/api/traffic/{name}", svr.apiProxyTraffic).Methods("GET")
  50. subRouter.HandleFunc("/api/clients", svr.apiClientList).Methods("GET")
  51. subRouter.HandleFunc("/api/clients/{key}", svr.apiClientDetail).Methods("GET")
  52. subRouter.HandleFunc("/api/proxies", svr.deleteProxies).Methods("DELETE")
  53. // view
  54. subRouter.Handle("/favicon.ico", http.FileServer(helper.AssetsFS)).Methods("GET")
  55. subRouter.PathPrefix("/static/").Handler(
  56. netpkg.MakeHTTPGzipHandler(http.StripPrefix("/static/", http.FileServer(helper.AssetsFS))),
  57. ).Methods("GET")
  58. subRouter.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  59. http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
  60. })
  61. }
  62. type serverInfoResp struct {
  63. Version string `json:"version"`
  64. BindPort int `json:"bindPort"`
  65. VhostHTTPPort int `json:"vhostHTTPPort"`
  66. VhostHTTPSPort int `json:"vhostHTTPSPort"`
  67. TCPMuxHTTPConnectPort int `json:"tcpmuxHTTPConnectPort"`
  68. KCPBindPort int `json:"kcpBindPort"`
  69. QUICBindPort int `json:"quicBindPort"`
  70. SubdomainHost string `json:"subdomainHost"`
  71. MaxPoolCount int64 `json:"maxPoolCount"`
  72. MaxPortsPerClient int64 `json:"maxPortsPerClient"`
  73. HeartBeatTimeout int64 `json:"heartbeatTimeout"`
  74. AllowPortsStr string `json:"allowPortsStr,omitempty"`
  75. TLSForce bool `json:"tlsForce,omitempty"`
  76. TotalTrafficIn int64 `json:"totalTrafficIn"`
  77. TotalTrafficOut int64 `json:"totalTrafficOut"`
  78. CurConns int64 `json:"curConns"`
  79. ClientCounts int64 `json:"clientCounts"`
  80. ProxyTypeCounts map[string]int64 `json:"proxyTypeCount"`
  81. }
  82. type clientInfoResp struct {
  83. Key string `json:"key"`
  84. User string `json:"user"`
  85. ClientID string `json:"clientId"`
  86. RunID string `json:"runId"`
  87. Hostname string `json:"hostname"`
  88. Metas map[string]string `json:"metas,omitempty"`
  89. FirstConnectedAt int64 `json:"firstConnectedAt"`
  90. LastConnectedAt int64 `json:"lastConnectedAt"`
  91. DisconnectedAt int64 `json:"disconnectedAt,omitempty"`
  92. Online bool `json:"online"`
  93. }
  94. // /healthz
  95. func (svr *Service) healthz(w http.ResponseWriter, _ *http.Request) {
  96. w.WriteHeader(200)
  97. }
  98. // /api/serverinfo
  99. func (svr *Service) apiServerInfo(w http.ResponseWriter, r *http.Request) {
  100. res := GeneralResponse{Code: 200}
  101. defer func() {
  102. log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
  103. w.WriteHeader(res.Code)
  104. if len(res.Msg) > 0 {
  105. _, _ = w.Write([]byte(res.Msg))
  106. }
  107. }()
  108. log.Infof("http request: [%s]", r.URL.Path)
  109. serverStats := mem.StatsCollector.GetServer()
  110. svrResp := serverInfoResp{
  111. Version: version.Full(),
  112. BindPort: svr.cfg.BindPort,
  113. VhostHTTPPort: svr.cfg.VhostHTTPPort,
  114. VhostHTTPSPort: svr.cfg.VhostHTTPSPort,
  115. TCPMuxHTTPConnectPort: svr.cfg.TCPMuxHTTPConnectPort,
  116. KCPBindPort: svr.cfg.KCPBindPort,
  117. QUICBindPort: svr.cfg.QUICBindPort,
  118. SubdomainHost: svr.cfg.SubDomainHost,
  119. MaxPoolCount: svr.cfg.Transport.MaxPoolCount,
  120. MaxPortsPerClient: svr.cfg.MaxPortsPerClient,
  121. HeartBeatTimeout: svr.cfg.Transport.HeartbeatTimeout,
  122. AllowPortsStr: types.PortsRangeSlice(svr.cfg.AllowPorts).String(),
  123. TLSForce: svr.cfg.Transport.TLS.Force,
  124. TotalTrafficIn: serverStats.TotalTrafficIn,
  125. TotalTrafficOut: serverStats.TotalTrafficOut,
  126. CurConns: serverStats.CurConns,
  127. ClientCounts: serverStats.ClientCounts,
  128. ProxyTypeCounts: serverStats.ProxyTypeCounts,
  129. }
  130. buf, _ := json.Marshal(&svrResp)
  131. res.Msg = string(buf)
  132. }
  133. // /api/clients
  134. func (svr *Service) apiClientList(w http.ResponseWriter, r *http.Request) {
  135. res := GeneralResponse{Code: 200}
  136. defer func() {
  137. log.Infof("http response [%s]: code [%d]", r.URL.RequestURI(), res.Code)
  138. w.WriteHeader(res.Code)
  139. if len(res.Msg) > 0 {
  140. _, _ = w.Write([]byte(res.Msg))
  141. }
  142. }()
  143. log.Infof("http request: [%s]", r.URL.RequestURI())
  144. if svr.clientRegistry == nil {
  145. res.Code = http.StatusInternalServerError
  146. res.Msg = "client registry unavailable"
  147. return
  148. }
  149. query := r.URL.Query()
  150. userFilter := query.Get("user")
  151. clientIDFilter := query.Get("clientId")
  152. runIDFilter := query.Get("runId")
  153. statusFilter := strings.ToLower(query.Get("status"))
  154. records := svr.clientRegistry.List()
  155. items := make([]clientInfoResp, 0, len(records))
  156. for _, info := range records {
  157. if userFilter != "" && info.User != userFilter {
  158. continue
  159. }
  160. if clientIDFilter != "" && info.ClientID != clientIDFilter {
  161. continue
  162. }
  163. if runIDFilter != "" && info.RunID != runIDFilter {
  164. continue
  165. }
  166. if !matchStatusFilter(info.Online, statusFilter) {
  167. continue
  168. }
  169. items = append(items, buildClientInfoResp(info))
  170. }
  171. slices.SortFunc(items, func(a, b clientInfoResp) int {
  172. if v := cmp.Compare(a.User, b.User); v != 0 {
  173. return v
  174. }
  175. if v := cmp.Compare(a.ClientID, b.ClientID); v != 0 {
  176. return v
  177. }
  178. return cmp.Compare(a.Key, b.Key)
  179. })
  180. buf, _ := json.Marshal(items)
  181. res.Msg = string(buf)
  182. }
  183. // /api/clients/{key}
  184. func (svr *Service) apiClientDetail(w http.ResponseWriter, r *http.Request) {
  185. res := GeneralResponse{Code: 200}
  186. defer func() {
  187. log.Infof("http response [%s]: code [%d]", r.URL.RequestURI(), res.Code)
  188. w.WriteHeader(res.Code)
  189. if len(res.Msg) > 0 {
  190. _, _ = w.Write([]byte(res.Msg))
  191. }
  192. }()
  193. log.Infof("http request: [%s]", r.URL.RequestURI())
  194. vars := mux.Vars(r)
  195. key := vars["key"]
  196. if key == "" {
  197. res.Code = http.StatusBadRequest
  198. res.Msg = "missing client key"
  199. return
  200. }
  201. if svr.clientRegistry == nil {
  202. res.Code = http.StatusInternalServerError
  203. res.Msg = "client registry unavailable"
  204. return
  205. }
  206. info, ok := svr.clientRegistry.GetByKey(key)
  207. if !ok {
  208. res.Code = http.StatusNotFound
  209. res.Msg = fmt.Sprintf("client %s not found", key)
  210. return
  211. }
  212. buf, _ := json.Marshal(buildClientInfoResp(info))
  213. res.Msg = string(buf)
  214. }
  215. type BaseOutConf struct {
  216. v1.ProxyBaseConfig
  217. }
  218. type TCPOutConf struct {
  219. BaseOutConf
  220. RemotePort int `json:"remotePort"`
  221. }
  222. type TCPMuxOutConf struct {
  223. BaseOutConf
  224. v1.DomainConfig
  225. Multiplexer string `json:"multiplexer"`
  226. RouteByHTTPUser string `json:"routeByHTTPUser"`
  227. }
  228. type UDPOutConf struct {
  229. BaseOutConf
  230. RemotePort int `json:"remotePort"`
  231. }
  232. type HTTPOutConf struct {
  233. BaseOutConf
  234. v1.DomainConfig
  235. Locations []string `json:"locations"`
  236. HostHeaderRewrite string `json:"hostHeaderRewrite"`
  237. }
  238. type HTTPSOutConf struct {
  239. BaseOutConf
  240. v1.DomainConfig
  241. }
  242. type STCPOutConf struct {
  243. BaseOutConf
  244. }
  245. type XTCPOutConf struct {
  246. BaseOutConf
  247. }
  248. func getConfByType(proxyType string) any {
  249. switch v1.ProxyType(proxyType) {
  250. case v1.ProxyTypeTCP:
  251. return &TCPOutConf{}
  252. case v1.ProxyTypeTCPMUX:
  253. return &TCPMuxOutConf{}
  254. case v1.ProxyTypeUDP:
  255. return &UDPOutConf{}
  256. case v1.ProxyTypeHTTP:
  257. return &HTTPOutConf{}
  258. case v1.ProxyTypeHTTPS:
  259. return &HTTPSOutConf{}
  260. case v1.ProxyTypeSTCP:
  261. return &STCPOutConf{}
  262. case v1.ProxyTypeXTCP:
  263. return &XTCPOutConf{}
  264. default:
  265. return nil
  266. }
  267. }
  268. // Get proxy info.
  269. type ProxyStatsInfo struct {
  270. Name string `json:"name"`
  271. Conf any `json:"conf"`
  272. ClientVersion string `json:"clientVersion,omitempty"`
  273. TodayTrafficIn int64 `json:"todayTrafficIn"`
  274. TodayTrafficOut int64 `json:"todayTrafficOut"`
  275. CurConns int64 `json:"curConns"`
  276. LastStartTime string `json:"lastStartTime"`
  277. LastCloseTime string `json:"lastCloseTime"`
  278. Status string `json:"status"`
  279. }
  280. type GetProxyInfoResp struct {
  281. Proxies []*ProxyStatsInfo `json:"proxies"`
  282. }
  283. // /api/proxy/:type
  284. func (svr *Service) apiProxyByType(w http.ResponseWriter, r *http.Request) {
  285. res := GeneralResponse{Code: 200}
  286. params := mux.Vars(r)
  287. proxyType := params["type"]
  288. defer func() {
  289. log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
  290. w.WriteHeader(res.Code)
  291. if len(res.Msg) > 0 {
  292. _, _ = w.Write([]byte(res.Msg))
  293. }
  294. }()
  295. log.Infof("http request: [%s]", r.URL.Path)
  296. proxyInfoResp := GetProxyInfoResp{}
  297. proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
  298. slices.SortFunc(proxyInfoResp.Proxies, func(a, b *ProxyStatsInfo) int {
  299. return cmp.Compare(a.Name, b.Name)
  300. })
  301. buf, _ := json.Marshal(&proxyInfoResp)
  302. res.Msg = string(buf)
  303. }
  304. func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
  305. proxyStats := mem.StatsCollector.GetProxiesByType(proxyType)
  306. proxyInfos = make([]*ProxyStatsInfo, 0, len(proxyStats))
  307. for _, ps := range proxyStats {
  308. proxyInfo := &ProxyStatsInfo{}
  309. if pxy, ok := svr.pxyManager.GetByName(ps.Name); ok {
  310. content, err := json.Marshal(pxy.GetConfigurer())
  311. if err != nil {
  312. log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
  313. continue
  314. }
  315. proxyInfo.Conf = getConfByType(ps.Type)
  316. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  317. log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  318. continue
  319. }
  320. proxyInfo.Status = "online"
  321. if pxy.GetLoginMsg() != nil {
  322. proxyInfo.ClientVersion = pxy.GetLoginMsg().Version
  323. }
  324. } else {
  325. proxyInfo.Status = "offline"
  326. }
  327. proxyInfo.Name = ps.Name
  328. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  329. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  330. proxyInfo.CurConns = ps.CurConns
  331. proxyInfo.LastStartTime = ps.LastStartTime
  332. proxyInfo.LastCloseTime = ps.LastCloseTime
  333. proxyInfos = append(proxyInfos, proxyInfo)
  334. }
  335. return
  336. }
  337. // Get proxy info by name.
  338. type GetProxyStatsResp struct {
  339. Name string `json:"name"`
  340. Conf any `json:"conf"`
  341. TodayTrafficIn int64 `json:"todayTrafficIn"`
  342. TodayTrafficOut int64 `json:"todayTrafficOut"`
  343. CurConns int64 `json:"curConns"`
  344. LastStartTime string `json:"lastStartTime"`
  345. LastCloseTime string `json:"lastCloseTime"`
  346. Status string `json:"status"`
  347. }
  348. // /api/proxy/:type/:name
  349. func (svr *Service) apiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
  350. res := GeneralResponse{Code: 200}
  351. params := mux.Vars(r)
  352. proxyType := params["type"]
  353. name := params["name"]
  354. defer func() {
  355. log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
  356. w.WriteHeader(res.Code)
  357. if len(res.Msg) > 0 {
  358. _, _ = w.Write([]byte(res.Msg))
  359. }
  360. }()
  361. log.Infof("http request: [%s]", r.URL.Path)
  362. var proxyStatsResp GetProxyStatsResp
  363. proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
  364. if res.Code != 200 {
  365. return
  366. }
  367. buf, _ := json.Marshal(&proxyStatsResp)
  368. res.Msg = string(buf)
  369. }
  370. func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
  371. proxyInfo.Name = proxyName
  372. ps := mem.StatsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
  373. if ps == nil {
  374. code = 404
  375. msg = "no proxy info found"
  376. } else {
  377. if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
  378. content, err := json.Marshal(pxy.GetConfigurer())
  379. if err != nil {
  380. log.Warnf("marshal proxy [%s] conf info error: %v", ps.Name, err)
  381. code = 400
  382. msg = "parse conf error"
  383. return
  384. }
  385. proxyInfo.Conf = getConfByType(ps.Type)
  386. if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
  387. log.Warnf("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
  388. code = 400
  389. msg = "parse conf error"
  390. return
  391. }
  392. proxyInfo.Status = "online"
  393. } else {
  394. proxyInfo.Status = "offline"
  395. }
  396. proxyInfo.TodayTrafficIn = ps.TodayTrafficIn
  397. proxyInfo.TodayTrafficOut = ps.TodayTrafficOut
  398. proxyInfo.CurConns = ps.CurConns
  399. proxyInfo.LastStartTime = ps.LastStartTime
  400. proxyInfo.LastCloseTime = ps.LastCloseTime
  401. code = 200
  402. }
  403. return
  404. }
  405. // /api/traffic/:name
  406. type GetProxyTrafficResp struct {
  407. Name string `json:"name"`
  408. TrafficIn []int64 `json:"trafficIn"`
  409. TrafficOut []int64 `json:"trafficOut"`
  410. }
  411. func (svr *Service) apiProxyTraffic(w http.ResponseWriter, r *http.Request) {
  412. res := GeneralResponse{Code: 200}
  413. params := mux.Vars(r)
  414. name := params["name"]
  415. defer func() {
  416. log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
  417. w.WriteHeader(res.Code)
  418. if len(res.Msg) > 0 {
  419. _, _ = w.Write([]byte(res.Msg))
  420. }
  421. }()
  422. log.Infof("http request: [%s]", r.URL.Path)
  423. trafficResp := GetProxyTrafficResp{}
  424. trafficResp.Name = name
  425. proxyTrafficInfo := mem.StatsCollector.GetProxyTraffic(name)
  426. if proxyTrafficInfo == nil {
  427. res.Code = 404
  428. res.Msg = "no proxy info found"
  429. return
  430. }
  431. trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
  432. trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
  433. buf, _ := json.Marshal(&trafficResp)
  434. res.Msg = string(buf)
  435. }
  436. // DELETE /api/proxies?status=offline
  437. func (svr *Service) deleteProxies(w http.ResponseWriter, r *http.Request) {
  438. res := GeneralResponse{Code: 200}
  439. log.Infof("http request: [%s]", r.URL.Path)
  440. defer func() {
  441. log.Infof("http response [%s]: code [%d]", r.URL.Path, res.Code)
  442. w.WriteHeader(res.Code)
  443. if len(res.Msg) > 0 {
  444. _, _ = w.Write([]byte(res.Msg))
  445. }
  446. }()
  447. status := r.URL.Query().Get("status")
  448. if status != "offline" {
  449. res.Code = 400
  450. res.Msg = "status only support offline"
  451. return
  452. }
  453. cleared, total := mem.StatsCollector.ClearOfflineProxies()
  454. log.Infof("cleared [%d] offline proxies, total [%d] proxies", cleared, total)
  455. }
  456. func buildClientInfoResp(info ClientInfo) clientInfoResp {
  457. resp := clientInfoResp{
  458. Key: info.Key,
  459. User: info.User,
  460. ClientID: info.ClientID,
  461. RunID: info.RunID,
  462. Hostname: info.Hostname,
  463. Metas: info.Metas,
  464. FirstConnectedAt: toUnix(info.FirstConnectedAt),
  465. LastConnectedAt: toUnix(info.LastConnectedAt),
  466. Online: info.Online,
  467. }
  468. if !info.DisconnectedAt.IsZero() {
  469. resp.DisconnectedAt = info.DisconnectedAt.Unix()
  470. }
  471. return resp
  472. }
  473. func toUnix(t time.Time) int64 {
  474. if t.IsZero() {
  475. return 0
  476. }
  477. return t.Unix()
  478. }
  479. func matchStatusFilter(online bool, filter string) bool {
  480. switch strings.ToLower(filter) {
  481. case "", "all":
  482. return true
  483. case "online":
  484. return online
  485. case "offline":
  486. return !online
  487. default:
  488. return true
  489. }
  490. }