client.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. // Copyright 2020 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 config
  15. import (
  16. "fmt"
  17. "os"
  18. "path/filepath"
  19. "strings"
  20. "gopkg.in/ini.v1"
  21. "github.com/fatedier/frp/pkg/auth"
  22. "github.com/fatedier/frp/pkg/util/util"
  23. )
  24. // ClientCommonConf contains information for a client service. It is
  25. // recommended to use GetDefaultClientConf instead of creating this object
  26. // directly, so that all unspecified fields have reasonable default values.
  27. type ClientCommonConf struct {
  28. auth.ClientConfig `ini:",extends"`
  29. // ServerAddr specifies the address of the server to connect to. By
  30. // default, this value is "0.0.0.0".
  31. ServerAddr string `ini:"server_addr" json:"server_addr"`
  32. // Specify another address of the server to connect for nat hole. By default, it's same with
  33. // ServerAddr.
  34. NatHoleServerAddr string `ini:"nat_hole_server_addr" json:"nat_hole_server_addr"`
  35. // ServerPort specifies the port to connect to the server on. By default,
  36. // this value is 7000.
  37. ServerPort int `ini:"server_port" json:"server_port"`
  38. // ServerUDPPort specifies the server port to help penetrate NAT hole. By default, this value is 0.
  39. // This parameter is only used when executing "nathole discover" in the command line.
  40. ServerUDPPort int `ini:"server_udp_port" json:"server_udp_port"`
  41. // STUN server to help penetrate NAT hole.
  42. NatHoleSTUNServer string `ini:"nat_hole_stun_server" json:"nat_hole_stun_server"`
  43. // The maximum amount of time a dial to server will wait for a connect to complete.
  44. DialServerTimeout int64 `ini:"dial_server_timeout" json:"dial_server_timeout"`
  45. // DialServerKeepAlive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
  46. // If negative, keep-alive probes are disabled.
  47. DialServerKeepAlive int64 `ini:"dial_server_keepalive" json:"dial_server_keepalive"`
  48. // ConnectServerLocalIP specifies the address of the client bind when it connect to server.
  49. // By default, this value is empty.
  50. // this value only use in TCP/Websocket protocol. Not support in KCP protocol.
  51. ConnectServerLocalIP string `ini:"connect_server_local_ip" json:"connect_server_local_ip"`
  52. // HTTPProxy specifies a proxy address to connect to the server through. If
  53. // this value is "", the server will be connected to directly. By default,
  54. // this value is read from the "http_proxy" environment variable.
  55. HTTPProxy string `ini:"http_proxy" json:"http_proxy"`
  56. // LogFile specifies a file where logs will be written to. This value will
  57. // only be used if LogWay is set appropriately. By default, this value is
  58. // "console".
  59. LogFile string `ini:"log_file" json:"log_file"`
  60. // LogWay specifies the way logging is managed. Valid values are "console"
  61. // or "file". If "console" is used, logs will be printed to stdout. If
  62. // "file" is used, logs will be printed to LogFile. By default, this value
  63. // is "console".
  64. LogWay string `ini:"log_way" json:"log_way"`
  65. // LogLevel specifies the minimum log level. Valid values are "trace",
  66. // "debug", "info", "warn", and "error". By default, this value is "info".
  67. LogLevel string `ini:"log_level" json:"log_level"`
  68. // LogMaxDays specifies the maximum number of days to store log information
  69. // before deletion. This is only used if LogWay == "file". By default, this
  70. // value is 0.
  71. LogMaxDays int64 `ini:"log_max_days" json:"log_max_days"`
  72. // DisableLogColor disables log colors when LogWay == "console" when set to
  73. // true. By default, this value is false.
  74. DisableLogColor bool `ini:"disable_log_color" json:"disable_log_color"`
  75. // AdminAddr specifies the address that the admin server binds to. By
  76. // default, this value is "127.0.0.1".
  77. AdminAddr string `ini:"admin_addr" json:"admin_addr"`
  78. // AdminPort specifies the port for the admin server to listen on. If this
  79. // value is 0, the admin server will not be started. By default, this value
  80. // is 0.
  81. AdminPort int `ini:"admin_port" json:"admin_port"`
  82. // AdminUser specifies the username that the admin server will use for
  83. // login.
  84. AdminUser string `ini:"admin_user" json:"admin_user"`
  85. // AdminPwd specifies the password that the admin server will use for
  86. // login.
  87. AdminPwd string `ini:"admin_pwd" json:"admin_pwd"`
  88. // AssetsDir specifies the local directory that the admin server will load
  89. // resources from. If this value is "", assets will be loaded from the
  90. // bundled executable using statik. By default, this value is "".
  91. AssetsDir string `ini:"assets_dir" json:"assets_dir"`
  92. // PoolCount specifies the number of connections the client will make to
  93. // the server in advance. By default, this value is 0.
  94. PoolCount int `ini:"pool_count" json:"pool_count"`
  95. // TCPMux toggles TCP stream multiplexing. This allows multiple requests
  96. // from a client to share a single TCP connection. If this value is true,
  97. // the server must have TCP multiplexing enabled as well. By default, this
  98. // value is true.
  99. TCPMux bool `ini:"tcp_mux" json:"tcp_mux"`
  100. // TCPMuxKeepaliveInterval specifies the keep alive interval for TCP stream multipler.
  101. // If TCPMux is true, heartbeat of application layer is unnecessary because it can only rely on heartbeat in TCPMux.
  102. TCPMuxKeepaliveInterval int64 `ini:"tcp_mux_keepalive_interval" json:"tcp_mux_keepalive_interval"`
  103. // User specifies a prefix for proxy names to distinguish them from other
  104. // clients. If this value is not "", proxy names will automatically be
  105. // changed to "{user}.{proxy_name}". By default, this value is "".
  106. User string `ini:"user" json:"user"`
  107. // DNSServer specifies a DNS server address for FRPC to use. If this value
  108. // is "", the default DNS will be used. By default, this value is "".
  109. DNSServer string `ini:"dns_server" json:"dns_server"`
  110. // LoginFailExit controls whether or not the client should exit after a
  111. // failed login attempt. If false, the client will retry until a login
  112. // attempt succeeds. By default, this value is true.
  113. LoginFailExit bool `ini:"login_fail_exit" json:"login_fail_exit"`
  114. // Start specifies a set of enabled proxies by name. If this set is empty,
  115. // all supplied proxies are enabled. By default, this value is an empty
  116. // set.
  117. Start []string `ini:"start" json:"start"`
  118. // Start map[string]struct{} `json:"start"`
  119. // Protocol specifies the protocol to use when interacting with the server.
  120. // Valid values are "tcp", "kcp", "quic" and "websocket". By default, this value
  121. // is "tcp".
  122. Protocol string `ini:"protocol" json:"protocol"`
  123. // QUIC protocol options
  124. QUICKeepalivePeriod int `ini:"quic_keepalive_period" json:"quic_keepalive_period" validate:"gte=0"`
  125. QUICMaxIdleTimeout int `ini:"quic_max_idle_timeout" json:"quic_max_idle_timeout" validate:"gte=0"`
  126. QUICMaxIncomingStreams int `ini:"quic_max_incoming_streams" json:"quic_max_incoming_streams" validate:"gte=0"`
  127. // TLSEnable specifies whether or not TLS should be used when communicating
  128. // with the server. If "tls_cert_file" and "tls_key_file" are valid,
  129. // client will load the supplied tls configuration.
  130. TLSEnable bool `ini:"tls_enable" json:"tls_enable"`
  131. // TLSCertPath specifies the path of the cert file that client will
  132. // load. It only works when "tls_enable" is true and "tls_key_file" is valid.
  133. TLSCertFile string `ini:"tls_cert_file" json:"tls_cert_file"`
  134. // TLSKeyPath specifies the path of the secret key file that client
  135. // will load. It only works when "tls_enable" is true and "tls_cert_file"
  136. // are valid.
  137. TLSKeyFile string `ini:"tls_key_file" json:"tls_key_file"`
  138. // TLSTrustedCaFile specifies the path of the trusted ca file that will load.
  139. // It only works when "tls_enable" is valid and tls configuration of server
  140. // has been specified.
  141. TLSTrustedCaFile string `ini:"tls_trusted_ca_file" json:"tls_trusted_ca_file"`
  142. // TLSServerName specifies the custom server name of tls certificate. By
  143. // default, server name if same to ServerAddr.
  144. TLSServerName string `ini:"tls_server_name" json:"tls_server_name"`
  145. // By default, frpc will connect frps with first custom byte if tls is enabled.
  146. // If DisableCustomTLSFirstByte is true, frpc will not send that custom byte.
  147. DisableCustomTLSFirstByte bool `ini:"disable_custom_tls_first_byte" json:"disable_custom_tls_first_byte"`
  148. // HeartBeatInterval specifies at what interval heartbeats are sent to the
  149. // server, in seconds. It is not recommended to change this value. By
  150. // default, this value is 30. Set negative value to disable it.
  151. HeartbeatInterval int64 `ini:"heartbeat_interval" json:"heartbeat_interval"`
  152. // HeartBeatTimeout specifies the maximum allowed heartbeat response delay
  153. // before the connection is terminated, in seconds. It is not recommended
  154. // to change this value. By default, this value is 90. Set negative value to disable it.
  155. HeartbeatTimeout int64 `ini:"heartbeat_timeout" json:"heartbeat_timeout"`
  156. // Client meta info
  157. Metas map[string]string `ini:"-" json:"metas"`
  158. // UDPPacketSize specifies the udp packet size
  159. // By default, this value is 1500
  160. UDPPacketSize int64 `ini:"udp_packet_size" json:"udp_packet_size"`
  161. // Include other config files for proxies.
  162. IncludeConfigFiles []string `ini:"includes" json:"includes"`
  163. // Enable golang pprof handlers in admin listener.
  164. // Admin port must be set first.
  165. PprofEnable bool `ini:"pprof_enable" json:"pprof_enable"`
  166. }
  167. // GetDefaultClientConf returns a client configuration with default values.
  168. func GetDefaultClientConf() ClientCommonConf {
  169. return ClientCommonConf{
  170. ClientConfig: auth.GetDefaultClientConf(),
  171. ServerAddr: "0.0.0.0",
  172. ServerPort: 7000,
  173. NatHoleSTUNServer: "stun.easyvoip.com:3478",
  174. DialServerTimeout: 10,
  175. DialServerKeepAlive: 7200,
  176. HTTPProxy: os.Getenv("http_proxy"),
  177. LogFile: "console",
  178. LogWay: "console",
  179. LogLevel: "info",
  180. LogMaxDays: 3,
  181. AdminAddr: "127.0.0.1",
  182. PoolCount: 1,
  183. TCPMux: true,
  184. TCPMuxKeepaliveInterval: 60,
  185. LoginFailExit: true,
  186. Start: make([]string, 0),
  187. Protocol: "tcp",
  188. QUICKeepalivePeriod: 10,
  189. QUICMaxIdleTimeout: 30,
  190. QUICMaxIncomingStreams: 100000,
  191. HeartbeatInterval: 30,
  192. HeartbeatTimeout: 90,
  193. Metas: make(map[string]string),
  194. UDPPacketSize: 1500,
  195. IncludeConfigFiles: make([]string, 0),
  196. }
  197. }
  198. func (cfg *ClientCommonConf) Complete() {
  199. if cfg.LogFile == "console" {
  200. cfg.LogWay = "console"
  201. } else {
  202. cfg.LogWay = "file"
  203. }
  204. }
  205. func (cfg *ClientCommonConf) Validate() error {
  206. if cfg.HeartbeatTimeout > 0 && cfg.HeartbeatInterval > 0 {
  207. if cfg.HeartbeatTimeout < cfg.HeartbeatInterval {
  208. return fmt.Errorf("invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
  209. }
  210. }
  211. if !cfg.TLSEnable {
  212. if cfg.TLSCertFile != "" {
  213. fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
  214. }
  215. if cfg.TLSKeyFile != "" {
  216. fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
  217. }
  218. if cfg.TLSTrustedCaFile != "" {
  219. fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
  220. }
  221. }
  222. if cfg.Protocol != "tcp" && cfg.Protocol != "kcp" && cfg.Protocol != "websocket" && cfg.Protocol != "quic" {
  223. return fmt.Errorf("invalid protocol")
  224. }
  225. for _, f := range cfg.IncludeConfigFiles {
  226. absDir, err := filepath.Abs(filepath.Dir(f))
  227. if err != nil {
  228. return fmt.Errorf("include: parse directory of %s failed: %v", f, absDir)
  229. }
  230. if _, err := os.Stat(absDir); os.IsNotExist(err) {
  231. return fmt.Errorf("include: directory of %s not exist", f)
  232. }
  233. }
  234. return nil
  235. }
  236. // Supported sources including: string(file path), []byte, Reader interface.
  237. func UnmarshalClientConfFromIni(source interface{}) (ClientCommonConf, error) {
  238. f, err := ini.LoadSources(ini.LoadOptions{
  239. Insensitive: false,
  240. InsensitiveSections: false,
  241. InsensitiveKeys: false,
  242. IgnoreInlineComment: true,
  243. AllowBooleanKeys: true,
  244. }, source)
  245. if err != nil {
  246. return ClientCommonConf{}, err
  247. }
  248. s, err := f.GetSection("common")
  249. if err != nil {
  250. return ClientCommonConf{}, fmt.Errorf("invalid configuration file, not found [common] section")
  251. }
  252. common := GetDefaultClientConf()
  253. err = s.MapTo(&common)
  254. if err != nil {
  255. return ClientCommonConf{}, err
  256. }
  257. common.Metas = GetMapWithoutPrefix(s.KeysHash(), "meta_")
  258. common.ClientConfig.OidcAdditionalEndpointParams = GetMapWithoutPrefix(s.KeysHash(), "oidc_additional_")
  259. return common, nil
  260. }
  261. // if len(startProxy) is 0, start all
  262. // otherwise just start proxies in startProxy map
  263. func LoadAllProxyConfsFromIni(
  264. prefix string,
  265. source interface{},
  266. start []string,
  267. ) (map[string]ProxyConf, map[string]VisitorConf, error) {
  268. f, err := ini.LoadSources(ini.LoadOptions{
  269. Insensitive: false,
  270. InsensitiveSections: false,
  271. InsensitiveKeys: false,
  272. IgnoreInlineComment: true,
  273. AllowBooleanKeys: true,
  274. }, source)
  275. if err != nil {
  276. return nil, nil, err
  277. }
  278. proxyConfs := make(map[string]ProxyConf)
  279. visitorConfs := make(map[string]VisitorConf)
  280. if prefix != "" {
  281. prefix += "."
  282. }
  283. startProxy := make(map[string]struct{})
  284. for _, s := range start {
  285. startProxy[s] = struct{}{}
  286. }
  287. startAll := true
  288. if len(startProxy) > 0 {
  289. startAll = false
  290. }
  291. // Build template sections from range section And append to ini.File.
  292. rangeSections := make([]*ini.Section, 0)
  293. for _, section := range f.Sections() {
  294. if !strings.HasPrefix(section.Name(), "range:") {
  295. continue
  296. }
  297. rangeSections = append(rangeSections, section)
  298. }
  299. for _, section := range rangeSections {
  300. err = renderRangeProxyTemplates(f, section)
  301. if err != nil {
  302. return nil, nil, fmt.Errorf("failed to render template for proxy %s: %v", section.Name(), err)
  303. }
  304. }
  305. for _, section := range f.Sections() {
  306. name := section.Name()
  307. if name == ini.DefaultSection || name == "common" || strings.HasPrefix(name, "range:") {
  308. continue
  309. }
  310. _, shouldStart := startProxy[name]
  311. if !startAll && !shouldStart {
  312. continue
  313. }
  314. roleType := section.Key("role").String()
  315. if roleType == "" {
  316. roleType = "server"
  317. }
  318. switch roleType {
  319. case "server":
  320. newConf, newErr := NewProxyConfFromIni(prefix, name, section)
  321. if newErr != nil {
  322. return nil, nil, fmt.Errorf("failed to parse proxy %s, err: %v", name, newErr)
  323. }
  324. proxyConfs[prefix+name] = newConf
  325. case "visitor":
  326. newConf, newErr := NewVisitorConfFromIni(prefix, name, section)
  327. if newErr != nil {
  328. return nil, nil, newErr
  329. }
  330. visitorConfs[prefix+name] = newConf
  331. default:
  332. return nil, nil, fmt.Errorf("proxy %s role should be 'server' or 'visitor'", name)
  333. }
  334. }
  335. return proxyConfs, visitorConfs, nil
  336. }
  337. func renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {
  338. // Validation
  339. localPortStr := section.Key("local_port").String()
  340. remotePortStr := section.Key("remote_port").String()
  341. if localPortStr == "" || remotePortStr == "" {
  342. return fmt.Errorf("local_port or remote_port is empty")
  343. }
  344. localPorts, err := util.ParseRangeNumbers(localPortStr)
  345. if err != nil {
  346. return err
  347. }
  348. remotePorts, err := util.ParseRangeNumbers(remotePortStr)
  349. if err != nil {
  350. return err
  351. }
  352. if len(localPorts) != len(remotePorts) {
  353. return fmt.Errorf("local ports number should be same with remote ports number")
  354. }
  355. if len(localPorts) == 0 {
  356. return fmt.Errorf("local_port and remote_port is necessary")
  357. }
  358. // Templates
  359. prefix := strings.TrimSpace(strings.TrimPrefix(section.Name(), "range:"))
  360. for i := range localPorts {
  361. tmpname := fmt.Sprintf("%s_%d", prefix, i)
  362. tmpsection, err := f.NewSection(tmpname)
  363. if err != nil {
  364. return err
  365. }
  366. copySection(section, tmpsection)
  367. if _, err := tmpsection.NewKey("local_port", fmt.Sprintf("%d", localPorts[i])); err != nil {
  368. return fmt.Errorf("local_port new key in section error: %v", err)
  369. }
  370. if _, err := tmpsection.NewKey("remote_port", fmt.Sprintf("%d", remotePorts[i])); err != nil {
  371. return fmt.Errorf("remote_port new key in section error: %v", err)
  372. }
  373. }
  374. return nil
  375. }
  376. func copySection(source, target *ini.Section) {
  377. for key, value := range source.KeysHash() {
  378. _, _ = target.NewKey(key, value)
  379. }
  380. }