frp/models/config/client_common.go
2020-09-18 20:06:33 +08:00

366 lines
12 KiB
Go

// 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"
"os"
"strconv"
"strings"
ini "github.com/vaughan0/go-ini"
"github.com/fatedier/frp/models/auth"
)
// 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
// ServerAddr specifies the address of the server to connect to. By
// default, this value is "0.0.0.0".
ServerAddr string `json:"server_addr"`
// ServerPort specifies the port to connect to the server on. By default,
// this value is 7000.
ServerPort int `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"`
// 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"`
// AdminAddr specifies the address that the admin server binds to. By
// default, this value is "127.0.0.1".
AdminAddr string `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"`
// AdminUser specifies the username that the admin server will use for
// login. By default, this value is "admin".
AdminUser string `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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// 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"`
// Client meta info
Metas map[string]string `json:"metas"`
// UDPPacketSize specifies the udp packet size
// By default, this value is 1500
UDPPacketSize int64 `json:"udp_packet_size"`
}
// GetDefaultClientConf returns a client configuration with default values.
func GetDefaultClientConf() ClientCommonConf {
return ClientCommonConf{
ServerAddr: "0.0.0.0",
ServerPort: 7000,
HTTPProxy: os.Getenv("http_proxy"),
LogFile: "console",
LogWay: "console",
LogLevel: "info",
LogMaxDays: 3,
DisableLogColor: false,
AdminAddr: "127.0.0.1",
AdminPort: 0,
AdminUser: "",
AdminPwd: "",
AssetsDir: "",
PoolCount: 1,
TCPMux: true,
User: "",
DNSServer: "",
LoginFailExit: true,
Start: make(map[string]struct{}),
Protocol: "tcp",
TLSEnable: false,
TLSCertFile: "",
TLSKeyFile: "",
TLSTrustedCaFile: "",
HeartBeatInterval: 30,
HeartBeatTimeout: 90,
Metas: make(map[string]string),
UDPPacketSize: 1500,
}
}
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) {
if cfg.HeartBeatInterval <= 0 {
err = fmt.Errorf("Parse conf error: invalid heartbeat_interval")
return
}
if cfg.HeartBeatTimeout < cfg.HeartBeatInterval {
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout, heartbeat_timeout is less than heartbeat_interval")
return
}
if cfg.TLSEnable == false {
if cfg.TLSCertFile != "" {
fmt.Println("WARNING! tls_cert_file is invalid when tls_enable is false")
}
if cfg.TLSKeyFile != "" {
fmt.Println("WARNING! tls_key_file is invalid when tls_enable is false")
}
if cfg.TLSTrustedCaFile != "" {
fmt.Println("WARNING! tls_trusted_ca_file is invalid when tls_enable is false")
}
}
return
}