mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
By default, a server is now sending to its clients the client URLs of all servers in the cluster. This allows clients to be able to reconnect to any server in the cluster even if those clients were not configured with the list of servers in the cluster. However, there may be cases where it would make sense to disable this feature. This now can be done with this option/command line parameter. Resolves #322
801 lines
20 KiB
Go
801 lines
20 KiB
Go
// Copyright 2012-2016 Apcera Inc. All rights reserved.
|
|
|
|
package server
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/nats-io/gnatsd/conf"
|
|
)
|
|
|
|
// For multiple accounts/users.
|
|
type User struct {
|
|
Username string `json:"user"`
|
|
Password string `json:"password"`
|
|
Permissions *Permissions `json:"permissions"`
|
|
}
|
|
|
|
// Authorization are the allowed subjects on a per
|
|
// publish or subscribe basis.
|
|
type Permissions struct {
|
|
Publish []string `json:"publish"`
|
|
Subscribe []string `json:"subscribe"`
|
|
}
|
|
|
|
// Options block for gnatsd server.
|
|
type Options struct {
|
|
Host string `json:"addr"`
|
|
Port int `json:"port"`
|
|
Trace bool `json:"-"`
|
|
Debug bool `json:"-"`
|
|
NoLog bool `json:"-"`
|
|
NoSigs bool `json:"-"`
|
|
Logtime bool `json:"-"`
|
|
MaxConn int `json:"max_connections"`
|
|
Users []*User `json:"-"`
|
|
Username string `json:"-"`
|
|
Password string `json:"-"`
|
|
Authorization string `json:"-"`
|
|
PingInterval time.Duration `json:"ping_interval"`
|
|
MaxPingsOut int `json:"ping_max"`
|
|
HTTPHost string `json:"http_host"`
|
|
HTTPPort int `json:"http_port"`
|
|
HTTPSPort int `json:"https_port"`
|
|
AuthTimeout float64 `json:"auth_timeout"`
|
|
MaxControlLine int `json:"max_control_line"`
|
|
MaxPayload int `json:"max_payload"`
|
|
MaxPending int `json:"max_pending_size"`
|
|
ClusterHost string `json:"addr"`
|
|
ClusterPort int `json:"cluster_port"`
|
|
ClusterUsername string `json:"-"`
|
|
ClusterPassword string `json:"-"`
|
|
ClusterAuthTimeout float64 `json:"auth_timeout"`
|
|
ClusterTLSTimeout float64 `json:"-"`
|
|
ClusterTLSConfig *tls.Config `json:"-"`
|
|
ClusterListenStr string `json:"-"`
|
|
ClusterNoAdvertise bool `json:"-"`
|
|
ProfPort int `json:"-"`
|
|
PidFile string `json:"-"`
|
|
LogFile string `json:"-"`
|
|
Syslog bool `json:"-"`
|
|
RemoteSyslog string `json:"-"`
|
|
Routes []*url.URL `json:"-"`
|
|
RoutesStr string `json:"-"`
|
|
TLSTimeout float64 `json:"tls_timeout"`
|
|
TLS bool `json:"-"`
|
|
TLSVerify bool `json:"-"`
|
|
TLSCert string `json:"-"`
|
|
TLSKey string `json:"-"`
|
|
TLSCaCert string `json:"-"`
|
|
TLSConfig *tls.Config `json:"-"`
|
|
}
|
|
|
|
// Configuration file authorization section.
|
|
type authorization struct {
|
|
// Singles
|
|
user string
|
|
pass string
|
|
// Multiple Users
|
|
users []*User
|
|
timeout float64
|
|
defaultPermissions *Permissions
|
|
}
|
|
|
|
// TLSConfigOpts holds the parsed tls config information,
|
|
// used with flag parsing
|
|
type TLSConfigOpts struct {
|
|
CertFile string
|
|
KeyFile string
|
|
CaFile string
|
|
Verify bool
|
|
Timeout float64
|
|
Ciphers []uint16
|
|
}
|
|
|
|
var tlsUsage = `
|
|
TLS configuration is specified in the tls section of a configuration file:
|
|
|
|
e.g.
|
|
|
|
tls {
|
|
cert_file: "./certs/server-cert.pem"
|
|
key_file: "./certs/server-key.pem"
|
|
ca_file: "./certs/ca.pem"
|
|
verify: true
|
|
|
|
cipher_suites: [
|
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
|
|
]
|
|
}
|
|
|
|
Available cipher suites include:
|
|
`
|
|
|
|
// ProcessConfigFile processes a configuration file.
|
|
// FIXME(dlc): Hacky
|
|
func ProcessConfigFile(configFile string) (*Options, error) {
|
|
opts := &Options{}
|
|
|
|
if configFile == "" {
|
|
return opts, nil
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(configFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error opening config file: %v", err)
|
|
}
|
|
|
|
m, err := conf.Parse(string(data))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for k, v := range m {
|
|
switch strings.ToLower(k) {
|
|
case "listen":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.Host = hp.host
|
|
opts.Port = hp.port
|
|
case "port":
|
|
opts.Port = int(v.(int64))
|
|
case "host", "net":
|
|
opts.Host = v.(string)
|
|
case "debug":
|
|
opts.Debug = v.(bool)
|
|
case "trace":
|
|
opts.Trace = v.(bool)
|
|
case "logtime":
|
|
opts.Logtime = v.(bool)
|
|
case "authorization":
|
|
am := v.(map[string]interface{})
|
|
auth, err := parseAuthorization(am)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.Username = auth.user
|
|
opts.Password = auth.pass
|
|
opts.AuthTimeout = auth.timeout
|
|
// Check for multiple users defined
|
|
if auth.users != nil {
|
|
if auth.user != "" {
|
|
return nil, fmt.Errorf("Can not have a single user/pass and a users array")
|
|
}
|
|
opts.Users = auth.users
|
|
}
|
|
case "http":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.HTTPHost = hp.host
|
|
opts.HTTPPort = hp.port
|
|
case "https":
|
|
hp, err := parseListen(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.HTTPHost = hp.host
|
|
opts.HTTPSPort = hp.port
|
|
case "http_port", "monitor_port":
|
|
opts.HTTPPort = int(v.(int64))
|
|
case "https_port":
|
|
opts.HTTPSPort = int(v.(int64))
|
|
case "cluster":
|
|
cm := v.(map[string]interface{})
|
|
if err := parseCluster(cm, opts); err != nil {
|
|
return nil, err
|
|
}
|
|
case "logfile", "log_file":
|
|
opts.LogFile = v.(string)
|
|
case "syslog":
|
|
opts.Syslog = v.(bool)
|
|
case "remote_syslog":
|
|
opts.RemoteSyslog = v.(string)
|
|
case "pidfile", "pid_file":
|
|
opts.PidFile = v.(string)
|
|
case "prof_port":
|
|
opts.ProfPort = int(v.(int64))
|
|
case "max_control_line":
|
|
opts.MaxControlLine = int(v.(int64))
|
|
case "max_payload":
|
|
opts.MaxPayload = int(v.(int64))
|
|
case "max_pending_size", "max_pending":
|
|
opts.MaxPending = int(v.(int64))
|
|
case "max_connections", "max_conn":
|
|
opts.MaxConn = int(v.(int64))
|
|
case "tls":
|
|
tlsm := v.(map[string]interface{})
|
|
tc, err := parseTLS(tlsm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if opts.TLSConfig, err = GenTLSConfig(tc); err != nil {
|
|
return nil, err
|
|
}
|
|
opts.TLSTimeout = tc.Timeout
|
|
}
|
|
}
|
|
return opts, nil
|
|
}
|
|
|
|
// hostPort is simple struct to hold parsed listen/addr strings.
|
|
type hostPort struct {
|
|
host string
|
|
port int
|
|
}
|
|
|
|
// parseListen will parse listen option which is replacing host/net and port
|
|
func parseListen(v interface{}) (*hostPort, error) {
|
|
hp := &hostPort{}
|
|
switch v.(type) {
|
|
// Only a port
|
|
case int64:
|
|
hp.port = int(v.(int64))
|
|
case string:
|
|
host, port, err := net.SplitHostPort(v.(string))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse address string %q", v)
|
|
}
|
|
hp.port, err = strconv.Atoi(port)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Could not parse port %q", port)
|
|
}
|
|
hp.host = host
|
|
}
|
|
return hp, nil
|
|
}
|
|
|
|
// parseCluster will parse the cluster config.
|
|
func parseCluster(cm map[string]interface{}, opts *Options) error {
|
|
for mk, mv := range cm {
|
|
switch strings.ToLower(mk) {
|
|
case "listen":
|
|
hp, err := parseListen(mv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
opts.ClusterHost = hp.host
|
|
opts.ClusterPort = hp.port
|
|
case "port":
|
|
opts.ClusterPort = int(mv.(int64))
|
|
case "host", "net":
|
|
opts.ClusterHost = mv.(string)
|
|
case "authorization":
|
|
am := mv.(map[string]interface{})
|
|
auth, err := parseAuthorization(am)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if auth.users != nil {
|
|
return fmt.Errorf("Cluster authorization does not allow multiple users")
|
|
}
|
|
opts.ClusterUsername = auth.user
|
|
opts.ClusterPassword = auth.pass
|
|
opts.ClusterAuthTimeout = auth.timeout
|
|
case "routes":
|
|
ra := mv.([]interface{})
|
|
opts.Routes = make([]*url.URL, 0, len(ra))
|
|
for _, r := range ra {
|
|
routeURL := r.(string)
|
|
url, err := url.Parse(routeURL)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing route url [%q]", routeURL)
|
|
}
|
|
opts.Routes = append(opts.Routes, url)
|
|
}
|
|
case "tls":
|
|
tlsm := mv.(map[string]interface{})
|
|
tc, err := parseTLS(tlsm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if opts.ClusterTLSConfig, err = GenTLSConfig(tc); err != nil {
|
|
return err
|
|
}
|
|
// For clusters, we will force strict verification. We also act
|
|
// as both client and server, so will mirror the rootCA to the
|
|
// clientCA pool.
|
|
opts.ClusterTLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
opts.ClusterTLSConfig.ClientCAs = opts.ClusterTLSConfig.RootCAs
|
|
opts.ClusterTLSTimeout = tc.Timeout
|
|
case "no_advertise":
|
|
opts.ClusterNoAdvertise = mv.(bool)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Helper function to parse Authorization configs.
|
|
func parseAuthorization(am map[string]interface{}) (*authorization, error) {
|
|
auth := &authorization{}
|
|
for mk, mv := range am {
|
|
switch strings.ToLower(mk) {
|
|
case "user", "username":
|
|
auth.user = mv.(string)
|
|
case "pass", "password":
|
|
auth.pass = mv.(string)
|
|
case "timeout":
|
|
at := float64(1)
|
|
switch mv.(type) {
|
|
case int64:
|
|
at = float64(mv.(int64))
|
|
case float64:
|
|
at = mv.(float64)
|
|
}
|
|
auth.timeout = at
|
|
case "users":
|
|
users, err := parseUsers(mv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth.users = users
|
|
case "default_permission", "default_permissions":
|
|
pm, ok := mv.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("Expected default permissions to be a map/struct, got %+v", mv)
|
|
}
|
|
permissions, err := parseUserPermissions(pm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
auth.defaultPermissions = permissions
|
|
}
|
|
|
|
// Now check for permission defaults with multiple users, etc.
|
|
if auth.users != nil && auth.defaultPermissions != nil {
|
|
for _, user := range auth.users {
|
|
if user.Permissions == nil {
|
|
user.Permissions = auth.defaultPermissions
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return auth, nil
|
|
}
|
|
|
|
// Helper function to parse multiple users array with optional permissions.
|
|
func parseUsers(mv interface{}) ([]*User, error) {
|
|
// Make sure we have an array
|
|
uv, ok := mv.([]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("Expected users field to be an array, got %v", mv)
|
|
}
|
|
users := []*User{}
|
|
for _, u := range uv {
|
|
// Check its a map/struct
|
|
um, ok := u.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("Expected user entry to be a map/struct, got %v", u)
|
|
}
|
|
user := &User{}
|
|
for k, v := range um {
|
|
switch strings.ToLower(k) {
|
|
case "user", "username":
|
|
user.Username = v.(string)
|
|
case "pass", "password":
|
|
user.Password = v.(string)
|
|
case "permission", "permissions", "authroization":
|
|
pm, ok := v.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("Expected user permissions to be a map/struct, got %+v", v)
|
|
}
|
|
permissions, err := parseUserPermissions(pm)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user.Permissions = permissions
|
|
}
|
|
}
|
|
// Check to make sure we have at least username and password
|
|
if user.Username == "" || user.Password == "" {
|
|
return nil, fmt.Errorf("User entry requires a user and a password")
|
|
}
|
|
users = append(users, user)
|
|
}
|
|
return users, nil
|
|
}
|
|
|
|
// Helper function to parse user/account permissions
|
|
func parseUserPermissions(pm map[string]interface{}) (*Permissions, error) {
|
|
p := &Permissions{}
|
|
for k, v := range pm {
|
|
switch strings.ToLower(k) {
|
|
case "pub", "publish":
|
|
subjects, err := parseSubjects(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Publish = subjects
|
|
case "sub", "subscribe":
|
|
subjects, err := parseSubjects(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Subscribe = subjects
|
|
default:
|
|
return nil, fmt.Errorf("Unknown field %s parsing permissions", k)
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// Helper function to parse subject singeltons and/or arrays
|
|
func parseSubjects(v interface{}) ([]string, error) {
|
|
var subjects []string
|
|
switch v.(type) {
|
|
case string:
|
|
subjects = append(subjects, v.(string))
|
|
case []string:
|
|
subjects = v.([]string)
|
|
case []interface{}:
|
|
for _, i := range v.([]interface{}) {
|
|
subject, ok := i.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Subject in permissions array cannot be cast to string")
|
|
}
|
|
subjects = append(subjects, subject)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("Expected subject permissions to be a subject, or array of subjects, got %T", v)
|
|
}
|
|
return checkSubjectArray(subjects)
|
|
}
|
|
|
|
// Helper function to validate subjects, etc for account permissioning.
|
|
func checkSubjectArray(sa []string) ([]string, error) {
|
|
for _, s := range sa {
|
|
if !IsValidSubject(s) {
|
|
return nil, fmt.Errorf("Subject %q is not a valid subject", s)
|
|
}
|
|
}
|
|
return sa, nil
|
|
}
|
|
|
|
// PrintTLSHelpAndDie prints TLS usage and exits.
|
|
func PrintTLSHelpAndDie() {
|
|
fmt.Printf("%s\n", tlsUsage)
|
|
for k := range cipherMap {
|
|
fmt.Printf(" %s\n", k)
|
|
}
|
|
fmt.Printf("\n")
|
|
os.Exit(0)
|
|
}
|
|
|
|
func parseCipher(cipherName string) (uint16, error) {
|
|
|
|
cipher, exists := cipherMap[cipherName]
|
|
if !exists {
|
|
return 0, fmt.Errorf("Unrecognized cipher %s", cipherName)
|
|
}
|
|
|
|
return cipher, nil
|
|
}
|
|
|
|
// Helper function to parse TLS configs.
|
|
func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) {
|
|
tc := TLSConfigOpts{}
|
|
for mk, mv := range tlsm {
|
|
switch strings.ToLower(mk) {
|
|
case "cert_file":
|
|
certFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'cert_file' to be filename")
|
|
}
|
|
tc.CertFile = certFile
|
|
case "key_file":
|
|
keyFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'key_file' to be filename")
|
|
}
|
|
tc.KeyFile = keyFile
|
|
case "ca_file":
|
|
caFile, ok := mv.(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'ca_file' to be filename")
|
|
}
|
|
tc.CaFile = caFile
|
|
case "verify":
|
|
verify, ok := mv.(bool)
|
|
if !ok {
|
|
return nil, fmt.Errorf("error parsing tls config, expected 'verify' to be a boolean")
|
|
}
|
|
tc.Verify = verify
|
|
case "cipher_suites":
|
|
ra := mv.([]interface{})
|
|
if len(ra) == 0 {
|
|
return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' cannot be empty")
|
|
}
|
|
tc.Ciphers = make([]uint16, 0, len(ra))
|
|
for _, r := range ra {
|
|
cipher, err := parseCipher(r.(string))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tc.Ciphers = append(tc.Ciphers, cipher)
|
|
}
|
|
case "timeout":
|
|
at := float64(0)
|
|
switch mv.(type) {
|
|
case int64:
|
|
at = float64(mv.(int64))
|
|
case float64:
|
|
at = mv.(float64)
|
|
}
|
|
tc.Timeout = at
|
|
default:
|
|
return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
|
|
}
|
|
}
|
|
|
|
// If cipher suites were not specified then use the defaults
|
|
if tc.Ciphers == nil {
|
|
tc.Ciphers = defaultCipherSuites()
|
|
}
|
|
|
|
return &tc, nil
|
|
}
|
|
|
|
// GenTLSConfig loads TLS related configuration parameters.
|
|
func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
|
|
|
|
// Now load in cert and private key
|
|
cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err)
|
|
}
|
|
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error parsing certificate: %v", err)
|
|
}
|
|
|
|
// Create TLSConfig
|
|
// We will determine the cipher suites that we prefer.
|
|
config := tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
PreferServerCipherSuites: true,
|
|
MinVersion: tls.VersionTLS12,
|
|
CipherSuites: tc.Ciphers,
|
|
}
|
|
|
|
// Require client certificates as needed
|
|
if tc.Verify {
|
|
config.ClientAuth = tls.RequireAnyClientCert
|
|
}
|
|
// Add in CAs if applicable.
|
|
if tc.CaFile != "" {
|
|
rootPEM, err := ioutil.ReadFile(tc.CaFile)
|
|
if err != nil || rootPEM == nil {
|
|
return nil, err
|
|
}
|
|
pool := x509.NewCertPool()
|
|
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to parse root ca certificate")
|
|
}
|
|
config.RootCAs = pool
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// MergeOptions will merge two options giving preference to the flagOpts
|
|
// if the item is present.
|
|
func MergeOptions(fileOpts, flagOpts *Options) *Options {
|
|
if fileOpts == nil {
|
|
return flagOpts
|
|
}
|
|
if flagOpts == nil {
|
|
return fileOpts
|
|
}
|
|
// Merge the two, flagOpts override
|
|
opts := *fileOpts
|
|
|
|
if flagOpts.Port != 0 {
|
|
opts.Port = flagOpts.Port
|
|
}
|
|
if flagOpts.Host != "" {
|
|
opts.Host = flagOpts.Host
|
|
}
|
|
if flagOpts.Username != "" {
|
|
opts.Username = flagOpts.Username
|
|
}
|
|
if flagOpts.Password != "" {
|
|
opts.Password = flagOpts.Password
|
|
}
|
|
if flagOpts.Authorization != "" {
|
|
opts.Authorization = flagOpts.Authorization
|
|
}
|
|
if flagOpts.HTTPPort != 0 {
|
|
opts.HTTPPort = flagOpts.HTTPPort
|
|
}
|
|
if flagOpts.Debug {
|
|
opts.Debug = true
|
|
}
|
|
if flagOpts.Trace {
|
|
opts.Trace = true
|
|
}
|
|
if flagOpts.Logtime {
|
|
opts.Logtime = true
|
|
}
|
|
if flagOpts.LogFile != "" {
|
|
opts.LogFile = flagOpts.LogFile
|
|
}
|
|
if flagOpts.PidFile != "" {
|
|
opts.PidFile = flagOpts.PidFile
|
|
}
|
|
if flagOpts.ProfPort != 0 {
|
|
opts.ProfPort = flagOpts.ProfPort
|
|
}
|
|
if flagOpts.ClusterListenStr != "" {
|
|
opts.ClusterListenStr = flagOpts.ClusterListenStr
|
|
}
|
|
if flagOpts.ClusterNoAdvertise {
|
|
opts.ClusterNoAdvertise = true
|
|
}
|
|
if flagOpts.RoutesStr != "" {
|
|
mergeRoutes(&opts, flagOpts)
|
|
}
|
|
return &opts
|
|
}
|
|
|
|
// RoutesFromStr parses route URLs from a string
|
|
func RoutesFromStr(routesStr string) []*url.URL {
|
|
routes := strings.Split(routesStr, ",")
|
|
if len(routes) == 0 {
|
|
return nil
|
|
}
|
|
routeUrls := []*url.URL{}
|
|
for _, r := range routes {
|
|
r = strings.TrimSpace(r)
|
|
u, _ := url.Parse(r)
|
|
routeUrls = append(routeUrls, u)
|
|
}
|
|
return routeUrls
|
|
}
|
|
|
|
// This will merge the flag routes and override anything that was present.
|
|
func mergeRoutes(opts, flagOpts *Options) {
|
|
routeUrls := RoutesFromStr(flagOpts.RoutesStr)
|
|
if routeUrls == nil {
|
|
return
|
|
}
|
|
opts.Routes = routeUrls
|
|
opts.RoutesStr = flagOpts.RoutesStr
|
|
}
|
|
|
|
// RemoveSelfReference removes this server from an array of routes
|
|
func RemoveSelfReference(clusterPort int, routes []*url.URL) ([]*url.URL, error) {
|
|
var cleanRoutes []*url.URL
|
|
cport := strconv.Itoa(clusterPort)
|
|
|
|
selfIPs := getInterfaceIPs()
|
|
for _, r := range routes {
|
|
host, port, err := net.SplitHostPort(r.Host)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if cport == port && isIPInList(selfIPs, getURLIP(host)) {
|
|
Noticef("Self referencing IP found: ", r)
|
|
continue
|
|
}
|
|
cleanRoutes = append(cleanRoutes, r)
|
|
}
|
|
|
|
return cleanRoutes, nil
|
|
}
|
|
|
|
func isIPInList(list1 []net.IP, list2 []net.IP) bool {
|
|
for _, ip1 := range list1 {
|
|
for _, ip2 := range list2 {
|
|
if ip1.Equal(ip2) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getURLIP(ipStr string) []net.IP {
|
|
ipList := []net.IP{}
|
|
|
|
ip := net.ParseIP(ipStr)
|
|
if ip != nil {
|
|
ipList = append(ipList, ip)
|
|
return ipList
|
|
}
|
|
|
|
hostAddr, err := net.LookupHost(ipStr)
|
|
if err != nil {
|
|
Errorf("Error looking up host with route hostname: %v", err)
|
|
return ipList
|
|
}
|
|
for _, addr := range hostAddr {
|
|
ip = net.ParseIP(addr)
|
|
if ip != nil {
|
|
ipList = append(ipList, ip)
|
|
}
|
|
}
|
|
return ipList
|
|
}
|
|
|
|
func getInterfaceIPs() []net.IP {
|
|
var localIPs []net.IP
|
|
|
|
interfaceAddr, err := net.InterfaceAddrs()
|
|
if err != nil {
|
|
Errorf("Error getting self referencing address: %v", err)
|
|
return localIPs
|
|
}
|
|
|
|
for i := 0; i < len(interfaceAddr); i++ {
|
|
interfaceIP, _, _ := net.ParseCIDR(interfaceAddr[i].String())
|
|
if net.ParseIP(interfaceIP.String()) != nil {
|
|
localIPs = append(localIPs, interfaceIP)
|
|
} else {
|
|
Errorf("Error parsing self referencing address: %v", err)
|
|
}
|
|
}
|
|
return localIPs
|
|
}
|
|
|
|
func processOptions(opts *Options) {
|
|
// Setup non-standard Go defaults
|
|
if opts.Host == "" {
|
|
opts.Host = DEFAULT_HOST
|
|
}
|
|
if opts.Port == 0 {
|
|
opts.Port = DEFAULT_PORT
|
|
} else if opts.Port == RANDOM_PORT {
|
|
// Choose randomly inside of net.Listen
|
|
opts.Port = 0
|
|
}
|
|
if opts.MaxConn == 0 {
|
|
opts.MaxConn = DEFAULT_MAX_CONNECTIONS
|
|
}
|
|
if opts.PingInterval == 0 {
|
|
opts.PingInterval = DEFAULT_PING_INTERVAL
|
|
}
|
|
if opts.MaxPingsOut == 0 {
|
|
opts.MaxPingsOut = DEFAULT_PING_MAX_OUT
|
|
}
|
|
if opts.TLSTimeout == 0 {
|
|
opts.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.AuthTimeout == 0 {
|
|
opts.AuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.ClusterHost == "" {
|
|
opts.ClusterHost = DEFAULT_HOST
|
|
}
|
|
if opts.ClusterTLSTimeout == 0 {
|
|
opts.ClusterTLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.ClusterAuthTimeout == 0 {
|
|
opts.ClusterAuthTimeout = float64(AUTH_TIMEOUT) / float64(time.Second)
|
|
}
|
|
if opts.MaxControlLine == 0 {
|
|
opts.MaxControlLine = MAX_CONTROL_LINE_SIZE
|
|
}
|
|
if opts.MaxPayload == 0 {
|
|
opts.MaxPayload = MAX_PAYLOAD_SIZE
|
|
}
|
|
if opts.MaxPending == 0 {
|
|
opts.MaxPending = MAX_PENDING_SIZE
|
|
}
|
|
}
|