root.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. // Copyright 2018 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 sub
  15. import (
  16. "bytes"
  17. "context"
  18. "fmt"
  19. "io/ioutil"
  20. "net"
  21. "os"
  22. "os/signal"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "syscall"
  27. "time"
  28. "github.com/fatedier/frp/client"
  29. "github.com/fatedier/frp/pkg/auth"
  30. "github.com/fatedier/frp/pkg/config"
  31. "github.com/fatedier/frp/pkg/util/log"
  32. "github.com/fatedier/frp/pkg/util/version"
  33. "github.com/spf13/cobra"
  34. )
  35. const (
  36. CfgFileTypeIni = iota
  37. CfgFileTypeCmd
  38. )
  39. var (
  40. cfgFile string
  41. showVersion bool
  42. serverAddr string
  43. user string
  44. protocol string
  45. token string
  46. logLevel string
  47. logFile string
  48. logMaxDays int
  49. disableLogColor bool
  50. proxyName string
  51. localIP string
  52. localPort int
  53. remotePort int
  54. useEncryption bool
  55. useCompression bool
  56. customDomains string
  57. subDomain string
  58. httpUser string
  59. httpPwd string
  60. locations string
  61. hostHeaderRewrite string
  62. role string
  63. sk string
  64. multiplexer string
  65. serverName string
  66. bindAddr string
  67. bindPort int
  68. tlsEnable bool
  69. kcpDoneCh chan struct{}
  70. )
  71. func init() {
  72. rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
  73. rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
  74. kcpDoneCh = make(chan struct{})
  75. }
  76. func RegisterCommonFlags(cmd *cobra.Command) {
  77. cmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
  78. cmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
  79. cmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
  80. cmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
  81. cmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
  82. cmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
  83. cmd.PersistentFlags().IntVarP(&logMaxDays, "log_max_days", "", 3, "log file reversed days")
  84. cmd.PersistentFlags().BoolVarP(&disableLogColor, "disable_log_color", "", false, "disable log color in console")
  85. cmd.PersistentFlags().BoolVarP(&tlsEnable, "tls_enable", "", false, "enable frpc tls")
  86. }
  87. var rootCmd = &cobra.Command{
  88. Use: "frpc",
  89. Short: "frpc is the client of frp (https://github.com/fatedier/frp)",
  90. RunE: func(cmd *cobra.Command, args []string) error {
  91. if showVersion {
  92. fmt.Println(version.Full())
  93. return nil
  94. }
  95. // Do not show command usage here.
  96. err := runClient(cfgFile)
  97. if err != nil {
  98. fmt.Println(err)
  99. os.Exit(1)
  100. }
  101. return nil
  102. },
  103. }
  104. func Execute() {
  105. if err := rootCmd.Execute(); err != nil {
  106. os.Exit(1)
  107. }
  108. }
  109. func handleSignal(svr *client.Service) {
  110. ch := make(chan os.Signal)
  111. signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
  112. <-ch
  113. svr.Close()
  114. time.Sleep(250 * time.Millisecond)
  115. close(kcpDoneCh)
  116. }
  117. func parseClientCommonCfg(fileType int, source []byte) (cfg config.ClientCommonConf, err error) {
  118. if fileType == CfgFileTypeIni {
  119. cfg, err = config.UnmarshalClientConfFromIni(source)
  120. } else if fileType == CfgFileTypeCmd {
  121. cfg, err = parseClientCommonCfgFromCmd()
  122. }
  123. if err != nil {
  124. return
  125. }
  126. cfg.Complete()
  127. err = cfg.Validate()
  128. if err != nil {
  129. err = fmt.Errorf("Parse config error: %v", err)
  130. return
  131. }
  132. return
  133. }
  134. func parseClientCommonCfgFromCmd() (cfg config.ClientCommonConf, err error) {
  135. cfg = config.GetDefaultClientConf()
  136. ipStr, portStr, err := net.SplitHostPort(serverAddr)
  137. if err != nil {
  138. err = fmt.Errorf("invalid server_addr: %v", err)
  139. return
  140. }
  141. cfg.ServerAddr = ipStr
  142. cfg.ServerPort, err = strconv.Atoi(portStr)
  143. if err != nil {
  144. err = fmt.Errorf("invalid server_addr: %v", err)
  145. return
  146. }
  147. cfg.User = user
  148. cfg.Protocol = protocol
  149. cfg.LogLevel = logLevel
  150. cfg.LogFile = logFile
  151. cfg.LogMaxDays = int64(logMaxDays)
  152. cfg.DisableLogColor = disableLogColor
  153. // Only token authentication is supported in cmd mode
  154. cfg.ClientConfig = auth.GetDefaultClientConf()
  155. cfg.Token = token
  156. cfg.TLSEnable = tlsEnable
  157. return
  158. }
  159. func runClient(cfgFilePath string) error {
  160. cfg, pxyCfgs, visitorCfgs, err := parseConfig(cfgFilePath)
  161. if err != nil {
  162. return err
  163. }
  164. return startService(cfg, pxyCfgs, visitorCfgs, cfgFilePath)
  165. }
  166. func parseConfig(cfgFilePath string) (
  167. cfg config.ClientCommonConf,
  168. pxyCfgs map[string]config.ProxyConf,
  169. visitorCfgs map[string]config.VisitorConf,
  170. err error,
  171. ) {
  172. var content []byte
  173. content, err = config.GetRenderedConfFromFile(cfgFilePath)
  174. if err != nil {
  175. return
  176. }
  177. configBuffer := bytes.NewBuffer(nil)
  178. configBuffer.Write(content)
  179. // Parse common section.
  180. cfg, err = parseClientCommonCfg(CfgFileTypeIni, content)
  181. if err != nil {
  182. return
  183. }
  184. // Aggregate proxy configs from include files.
  185. var buf []byte
  186. buf, err = getIncludeContents(cfg.IncludeConfigFiles)
  187. if err != nil {
  188. err = fmt.Errorf("getIncludeContents error: %v", err)
  189. return
  190. }
  191. configBuffer.WriteString("\n")
  192. configBuffer.Write(buf)
  193. // Parse all proxy and visitor configs.
  194. pxyCfgs, visitorCfgs, err = config.LoadAllProxyConfsFromIni(cfg.User, configBuffer.Bytes(), cfg.Start)
  195. if err != nil {
  196. return
  197. }
  198. return
  199. }
  200. // getIncludeContents renders all configs from paths.
  201. // files format can be a single file path or directory or regex path.
  202. func getIncludeContents(paths []string) ([]byte, error) {
  203. out := bytes.NewBuffer(nil)
  204. for _, path := range paths {
  205. absDir, err := filepath.Abs(filepath.Dir(path))
  206. if err != nil {
  207. return nil, err
  208. }
  209. if _, err := os.Stat(absDir); os.IsNotExist(err) {
  210. return nil, err
  211. }
  212. files, err := ioutil.ReadDir(absDir)
  213. if err != nil {
  214. return nil, err
  215. }
  216. for _, fi := range files {
  217. if fi.IsDir() {
  218. continue
  219. }
  220. absFile := filepath.Join(absDir, fi.Name())
  221. if matched, _ := filepath.Match(filepath.Join(absDir, filepath.Base(path)), absFile); matched {
  222. tmpContent, err := config.GetRenderedConfFromFile(absFile)
  223. if err != nil {
  224. return nil, fmt.Errorf("render extra config %s error: %v", absFile, err)
  225. }
  226. out.Write(tmpContent)
  227. out.WriteString("\n")
  228. }
  229. }
  230. }
  231. return out.Bytes(), nil
  232. }
  233. func startService(
  234. cfg config.ClientCommonConf,
  235. pxyCfgs map[string]config.ProxyConf,
  236. visitorCfgs map[string]config.VisitorConf,
  237. cfgFile string,
  238. ) (err error) {
  239. log.InitLog(cfg.LogWay, cfg.LogFile, cfg.LogLevel,
  240. cfg.LogMaxDays, cfg.DisableLogColor)
  241. if cfg.DNSServer != "" {
  242. s := cfg.DNSServer
  243. if !strings.Contains(s, ":") {
  244. s += ":53"
  245. }
  246. // Change default dns server for frpc
  247. net.DefaultResolver = &net.Resolver{
  248. PreferGo: true,
  249. Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
  250. return net.Dial("udp", s)
  251. },
  252. }
  253. }
  254. svr, errRet := client.NewService(cfg, pxyCfgs, visitorCfgs, cfgFile)
  255. if errRet != nil {
  256. err = errRet
  257. return
  258. }
  259. // Capture the exit signal if we use kcp.
  260. if cfg.Protocol == "kcp" {
  261. go handleSignal(svr)
  262. }
  263. err = svr.Run()
  264. if err == nil && cfg.Protocol == "kcp" {
  265. <-kcpDoneCh
  266. }
  267. return
  268. }