mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 11:48:43 -07:00
- When detecting duplicate route, it was possible that a server would lose track of the peer's gateway URL, which would prevent it from gossiping that URL to inbound gateway connections - When a server has gateways enabled and has as a remote its own gateway, the monitoring endpoint `/varz` would include it but without the "urls" array. Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
274 lines
7.3 KiB
Go
274 lines
7.3 KiB
Go
// Copyright 2012-2019 The NATS Authors
|
|
// 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 server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"net"
|
|
"net/url"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// This map is used to store URLs string as the key with a reference count as
|
|
// the value. This is used to handle gossiped URLs such as connect_urls, etc..
|
|
type refCountedUrlSet map[string]int
|
|
|
|
// Ascii numbers 0-9
|
|
const (
|
|
asciiZero = 48
|
|
asciiNine = 57
|
|
)
|
|
|
|
// parseSize expects decimal positive numbers. We
|
|
// return -1 to signal error.
|
|
func parseSize(d []byte) (n int) {
|
|
const maxParseSizeLen = 9 //999M
|
|
|
|
l := len(d)
|
|
if l == 0 || l > maxParseSizeLen {
|
|
return -1
|
|
}
|
|
var (
|
|
i int
|
|
dec byte
|
|
)
|
|
|
|
// Note: Use `goto` here to avoid for loop in order
|
|
// to have the function be inlined.
|
|
// See: https://github.com/golang/go/issues/14768
|
|
loop:
|
|
dec = d[i]
|
|
if dec < asciiZero || dec > asciiNine {
|
|
return -1
|
|
}
|
|
n = n*10 + (int(dec) - asciiZero)
|
|
|
|
i++
|
|
if i < l {
|
|
goto loop
|
|
}
|
|
return n
|
|
}
|
|
|
|
// parseInt64 expects decimal positive numbers. We
|
|
// return -1 to signal error
|
|
func parseInt64(d []byte) (n int64) {
|
|
if len(d) == 0 {
|
|
return -1
|
|
}
|
|
for _, dec := range d {
|
|
if dec < asciiZero || dec > asciiNine {
|
|
return -1
|
|
}
|
|
n = n*10 + (int64(dec) - asciiZero)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Helper to move from float seconds to time.Duration
|
|
func secondsToDuration(seconds float64) time.Duration {
|
|
ttl := seconds * float64(time.Second)
|
|
return time.Duration(ttl)
|
|
}
|
|
|
|
// Parse a host/port string with a default port to use
|
|
// if none (or 0 or -1) is specified in `hostPort` string.
|
|
func parseHostPort(hostPort string, defaultPort int) (host string, port int, err error) {
|
|
if hostPort != "" {
|
|
host, sPort, err := net.SplitHostPort(hostPort)
|
|
if ae, ok := err.(*net.AddrError); ok && strings.Contains(ae.Err, "missing port") {
|
|
// try appending the current port
|
|
host, sPort, err = net.SplitHostPort(fmt.Sprintf("%s:%d", hostPort, defaultPort))
|
|
}
|
|
if err != nil {
|
|
return "", -1, err
|
|
}
|
|
port, err = strconv.Atoi(strings.TrimSpace(sPort))
|
|
if err != nil {
|
|
return "", -1, err
|
|
}
|
|
if port == 0 || port == -1 {
|
|
port = defaultPort
|
|
}
|
|
return strings.TrimSpace(host), port, nil
|
|
}
|
|
return "", -1, errors.New("no hostport specified")
|
|
}
|
|
|
|
// Returns true if URL u1 represents the same URL than u2,
|
|
// false otherwise.
|
|
func urlsAreEqual(u1, u2 *url.URL) bool {
|
|
return reflect.DeepEqual(u1, u2)
|
|
}
|
|
|
|
// comma produces a string form of the given number in base 10 with
|
|
// commas after every three orders of magnitude.
|
|
//
|
|
// e.g. comma(834142) -> 834,142
|
|
//
|
|
// This function was copied from the github.com/dustin/go-humanize
|
|
// package and is Copyright Dustin Sallings <dustin@spy.net>
|
|
func comma(v int64) string {
|
|
sign := ""
|
|
|
|
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
|
if v == math.MinInt64 {
|
|
return "-9,223,372,036,854,775,808"
|
|
}
|
|
|
|
if v < 0 {
|
|
sign = "-"
|
|
v = 0 - v
|
|
}
|
|
|
|
parts := []string{"", "", "", "", "", "", ""}
|
|
j := len(parts) - 1
|
|
|
|
for v > 999 {
|
|
parts[j] = strconv.FormatInt(v%1000, 10)
|
|
switch len(parts[j]) {
|
|
case 2:
|
|
parts[j] = "0" + parts[j]
|
|
case 1:
|
|
parts[j] = "00" + parts[j]
|
|
}
|
|
v = v / 1000
|
|
j--
|
|
}
|
|
parts[j] = strconv.Itoa(int(v))
|
|
return sign + strings.Join(parts[j:], ",")
|
|
}
|
|
|
|
// Adds urlStr to the given map. If the string was already present, simply
|
|
// bumps the reference count.
|
|
// Returns true only if it was added for the first time.
|
|
func (m refCountedUrlSet) addUrl(urlStr string) bool {
|
|
m[urlStr]++
|
|
return m[urlStr] == 1
|
|
}
|
|
|
|
// Removes urlStr from the given map. If the string is not present, nothing
|
|
// is done and false is returned.
|
|
// If the string was present, its reference count is decreased. Returns true
|
|
// if this was the last reference, false otherwise.
|
|
func (m refCountedUrlSet) removeUrl(urlStr string) bool {
|
|
removed := false
|
|
if ref, ok := m[urlStr]; ok {
|
|
if ref == 1 {
|
|
removed = true
|
|
delete(m, urlStr)
|
|
} else {
|
|
m[urlStr]--
|
|
}
|
|
}
|
|
return removed
|
|
}
|
|
|
|
// Returns the unique URLs in this map as a slice
|
|
func (m refCountedUrlSet) getAsStringSlice() []string {
|
|
a := make([]string, 0, len(m))
|
|
for u := range m {
|
|
a = append(a, u)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// natsListenConfig provides a common configuration to match the one used by
|
|
// net.Listen() but with our own defaults.
|
|
// Go 1.13 introduced default-on TCP keepalives with aggressive timings and
|
|
// there's no sane portable way in Go with stdlib to split the initial timer
|
|
// from the retry timer. Linux/BSD defaults are 2hrs/75s and Go sets both
|
|
// to 15s; the issue re making them indepedently tunable has been open since
|
|
// 2014 and this code here is being written in 2020.
|
|
// The NATS protocol has its own L7 PING/PONG keepalive system and the Go
|
|
// defaults are inappropriate for IoT deployment scenarios.
|
|
// Replace any NATS-protocol calls to net.Listen(...) with
|
|
// natsListenConfig.Listen(ctx,...) or use natsListen(); leave calls for HTTP
|
|
// monitoring, etc, on the default.
|
|
var natsListenConfig = &net.ListenConfig{
|
|
KeepAlive: -1,
|
|
}
|
|
|
|
// natsListen() is the same as net.Listen() except that TCP keepalives are
|
|
// disabled (to match Go's behavior before Go 1.13).
|
|
func natsListen(network, address string) (net.Listener, error) {
|
|
return natsListenConfig.Listen(context.Background(), network, address)
|
|
}
|
|
|
|
// natsDialTimeout is the same as net.DialTimeout() except the TCP keepalives
|
|
// are disabled (to match Go's behavior before Go 1.13).
|
|
func natsDialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
|
d := net.Dialer{
|
|
Timeout: timeout,
|
|
KeepAlive: -1,
|
|
}
|
|
return d.Dial(network, address)
|
|
}
|
|
|
|
// redactURLList() returns a copy of a list of URL pointers where each item
|
|
// in the list will either be the same pointer if the URL does not contain a
|
|
// password, or to a new object if there is a password.
|
|
// The intended use-case is for logging lists of URLs safely.
|
|
func redactURLList(unredacted []*url.URL) []*url.URL {
|
|
r := make([]*url.URL, len(unredacted))
|
|
// In the common case of no passwords, if we don't let the new object leave
|
|
// this function then GC should be easier.
|
|
needCopy := false
|
|
for i := range unredacted {
|
|
if unredacted[i] == nil {
|
|
r[i] = nil
|
|
continue
|
|
}
|
|
if _, has := unredacted[i].User.Password(); !has {
|
|
r[i] = unredacted[i]
|
|
continue
|
|
}
|
|
needCopy = true
|
|
ru := *unredacted[i]
|
|
ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
|
|
r[i] = &ru
|
|
}
|
|
if needCopy {
|
|
return r
|
|
}
|
|
return unredacted
|
|
}
|
|
|
|
// redactURLString() attempts to redact a URL string.
|
|
func redactURLString(raw string) string {
|
|
if !strings.ContainsRune(raw, '@') {
|
|
return raw
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return raw
|
|
}
|
|
return u.Redacted()
|
|
}
|
|
|
|
// getURLsAsString returns a slice of u.Host from the given slice of url.URL's
|
|
func getURLsAsString(urls []*url.URL) []string {
|
|
a := make([]string, 0, len(urls))
|
|
for _, u := range urls {
|
|
a = append(a, u.Host)
|
|
}
|
|
return a
|
|
}
|