2015-10-31 18:56:49 +00:00
|
|
|
/*
|
|
|
|
Copyright 2015 Gravitational, Inc.
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2016-03-09 00:29:08 +00:00
|
|
|
|
2015-05-07 03:10:44 +00:00
|
|
|
package utils
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2016-02-09 04:55:13 +00:00
|
|
|
"net"
|
2015-05-07 03:10:44 +00:00
|
|
|
"net/url"
|
2016-02-09 04:55:13 +00:00
|
|
|
"strconv"
|
2015-05-07 03:10:44 +00:00
|
|
|
"strings"
|
2016-02-09 21:46:34 +00:00
|
|
|
|
|
|
|
"github.com/gravitational/trace"
|
2017-08-22 22:30:30 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2015-05-07 03:10:44 +00:00
|
|
|
)
|
|
|
|
|
2016-02-20 04:56:25 +00:00
|
|
|
// NetAddr is network address that includes network, optional path and
|
|
|
|
// host port
|
2015-05-07 03:10:44 +00:00
|
|
|
type NetAddr struct {
|
2016-02-10 00:09:21 +00:00
|
|
|
// Addr is the host:port address, like "localhost:22"
|
2016-02-20 04:56:25 +00:00
|
|
|
Addr string `json:"addr"`
|
2016-02-10 00:09:21 +00:00
|
|
|
// AddrNetwork is the type of a network socket, like "tcp" or "unix"
|
2016-02-20 04:56:25 +00:00
|
|
|
AddrNetwork string `json:"network,omitempty"`
|
2016-02-10 00:09:21 +00:00
|
|
|
// Path is a socket file path, like '/var/path/to/socket' in "unix:///var/path/to/socket"
|
2016-02-20 04:56:25 +00:00
|
|
|
Path string `json:"path,omitempty"`
|
2015-05-07 03:10:44 +00:00
|
|
|
}
|
|
|
|
|
2018-08-02 00:25:16 +00:00
|
|
|
// Host returns host part of address without port
|
|
|
|
func (a *NetAddr) Host() string {
|
|
|
|
host, _, err := net.SplitHostPort(a.Addr)
|
2019-07-19 17:55:11 +00:00
|
|
|
if err == nil {
|
|
|
|
return host
|
|
|
|
}
|
|
|
|
// this is done to remove optional square brackets
|
|
|
|
if ip := net.ParseIP(strings.Trim(a.Addr, "[]")); len(ip) != 0 {
|
|
|
|
return ip.String()
|
2018-08-02 00:25:16 +00:00
|
|
|
}
|
2019-07-19 17:55:11 +00:00
|
|
|
return a.Addr
|
2018-08-02 00:25:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Port returns defaultPort if no port is set or is invalid,
|
|
|
|
// the real port otherwise
|
|
|
|
func (a *NetAddr) Port(defaultPort int) int {
|
|
|
|
_, port, err := net.SplitHostPort(a.Addr)
|
|
|
|
if err != nil {
|
|
|
|
return defaultPort
|
|
|
|
}
|
|
|
|
porti, err := strconv.Atoi(port)
|
|
|
|
if err != nil {
|
|
|
|
return defaultPort
|
|
|
|
}
|
|
|
|
return porti
|
|
|
|
}
|
|
|
|
|
2017-10-11 22:31:19 +00:00
|
|
|
// Equals returns true if address is equal to other
|
|
|
|
func (a *NetAddr) Equals(other NetAddr) bool {
|
|
|
|
return a.Addr == other.Addr && a.AddrNetwork == other.AddrNetwork && a.Path == other.Path
|
|
|
|
}
|
|
|
|
|
2016-03-23 18:12:24 +00:00
|
|
|
// IsLocal returns true if this is a local address
|
|
|
|
func (a *NetAddr) IsLocal() bool {
|
|
|
|
host, _, err := net.SplitHostPort(a.Addr)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
2016-03-24 01:08:17 +00:00
|
|
|
return IsLocalhost(host)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsLoopback returns true if this is a loopback address
|
|
|
|
func (a *NetAddr) IsLoopback() bool {
|
2016-03-24 01:23:48 +00:00
|
|
|
return IsLoopback(a.Addr)
|
2016-03-23 18:12:24 +00:00
|
|
|
}
|
|
|
|
|
2016-02-20 04:56:25 +00:00
|
|
|
// IsEmpty returns true if address is empty
|
2015-10-13 00:50:36 +00:00
|
|
|
func (a *NetAddr) IsEmpty() bool {
|
2020-10-30 17:19:53 +00:00
|
|
|
return a == nil || (a.Addr == "" && a.AddrNetwork == "" && a.Path == "")
|
2015-10-13 00:50:36 +00:00
|
|
|
}
|
|
|
|
|
2015-12-17 21:40:42 +00:00
|
|
|
// FullAddress returns full address including network and address (tcp://0.0.0.0:1243)
|
|
|
|
func (a *NetAddr) FullAddress() string {
|
|
|
|
return fmt.Sprintf("%v://%v", a.AddrNetwork, a.Addr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns address without network (0.0.0.0:1234)
|
2015-05-07 03:10:44 +00:00
|
|
|
func (a *NetAddr) String() string {
|
2015-12-17 21:40:42 +00:00
|
|
|
return a.Addr
|
|
|
|
}
|
|
|
|
|
2016-06-10 07:11:31 +00:00
|
|
|
// Network returns the scheme for this network address (tcp or unix)
|
2015-12-17 21:40:42 +00:00
|
|
|
func (a *NetAddr) Network() string {
|
|
|
|
return a.AddrNetwork
|
2015-05-07 03:10:44 +00:00
|
|
|
}
|
|
|
|
|
2016-06-10 07:11:31 +00:00
|
|
|
// MarshalYAML defines how a network address should be marshalled to a string
|
|
|
|
func (a *NetAddr) MarshalYAML() (interface{}, error) {
|
|
|
|
url := url.URL{Scheme: a.AddrNetwork, Host: a.Addr, Path: a.Path}
|
|
|
|
return strings.TrimLeft(url.String(), "/"), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalYAML defines how a string can be unmarshalled into a network address
|
2015-10-13 00:50:36 +00:00
|
|
|
func (a *NetAddr) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
|
|
var addr string
|
|
|
|
err := unmarshal(&addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedAddr, err := ParseAddr(addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*a = *parsedAddr
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *NetAddr) Set(s string) error {
|
|
|
|
v, err := ParseAddr(s)
|
|
|
|
if err != nil {
|
2018-11-20 01:36:19 +00:00
|
|
|
return trace.Wrap(err)
|
2015-10-13 00:50:36 +00:00
|
|
|
}
|
|
|
|
a.Addr = v.Addr
|
2015-12-17 21:40:42 +00:00
|
|
|
a.AddrNetwork = v.AddrNetwork
|
2015-10-13 00:50:36 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-24 21:01:30 +00:00
|
|
|
// ParseAddrs parses the provided slice of strings as a slice of NetAddr's.
|
|
|
|
func ParseAddrs(addrs []string) (result []NetAddr, err error) {
|
|
|
|
for _, addr := range addrs {
|
|
|
|
parsed, err := ParseAddr(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
result = append(result, *parsed)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2016-02-09 04:55:13 +00:00
|
|
|
// ParseAddr takes strings like "tcp://host:port/path" and returns
|
|
|
|
// *NetAddr or an error
|
2015-05-07 03:10:44 +00:00
|
|
|
func ParseAddr(a string) (*NetAddr, error) {
|
2018-08-02 00:25:16 +00:00
|
|
|
if a == "" {
|
|
|
|
return nil, trace.BadParameter("missing parameter address")
|
|
|
|
}
|
2016-03-11 01:03:01 +00:00
|
|
|
if !strings.Contains(a, "://") {
|
2018-08-02 00:25:16 +00:00
|
|
|
return &NetAddr{Addr: a, AddrNetwork: "tcp"}, nil
|
2016-03-11 01:03:01 +00:00
|
|
|
}
|
2015-05-07 03:10:44 +00:00
|
|
|
u, err := url.Parse(a)
|
|
|
|
if err != nil {
|
2018-08-02 00:25:16 +00:00
|
|
|
return nil, trace.BadParameter("failed to parse %q: %v", a, err)
|
2015-05-07 03:10:44 +00:00
|
|
|
}
|
|
|
|
switch u.Scheme {
|
|
|
|
case "tcp":
|
2015-12-17 21:40:42 +00:00
|
|
|
return &NetAddr{Addr: u.Host, AddrNetwork: u.Scheme, Path: u.Path}, nil
|
2015-05-07 03:10:44 +00:00
|
|
|
case "unix":
|
2015-12-17 21:40:42 +00:00
|
|
|
return &NetAddr{Addr: u.Path, AddrNetwork: u.Scheme}, nil
|
2018-02-08 02:32:50 +00:00
|
|
|
case "http", "https":
|
|
|
|
return &NetAddr{Addr: u.Host, AddrNetwork: u.Scheme, Path: u.Path}, nil
|
2015-05-07 03:10:44 +00:00
|
|
|
default:
|
2016-04-12 17:54:24 +00:00
|
|
|
return nil, trace.BadParameter("'%v': unsupported scheme: '%v'", a, u.Scheme)
|
2015-05-07 03:10:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-10 07:11:31 +00:00
|
|
|
// MustParseAddr parses the provided string into NetAddr or panics on an error
|
|
|
|
func MustParseAddr(a string) *NetAddr {
|
|
|
|
addr, err := ParseAddr(a)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("failed to parse %v: %v", a, err))
|
|
|
|
}
|
|
|
|
return addr
|
|
|
|
}
|
|
|
|
|
2020-11-03 21:50:22 +00:00
|
|
|
// MustParseAddrList parses the provided list of strings into a NetAddr list or panics on error
|
|
|
|
func MustParseAddrList(aList ...string) []NetAddr {
|
|
|
|
addrList := make([]NetAddr, len(aList))
|
|
|
|
for i, a := range aList {
|
|
|
|
addrList[i] = *MustParseAddr(a)
|
|
|
|
}
|
|
|
|
return addrList
|
|
|
|
}
|
|
|
|
|
2017-11-25 01:09:11 +00:00
|
|
|
// FromAddr returns NetAddr from golang standard net.Addr
|
|
|
|
func FromAddr(a net.Addr) NetAddr {
|
|
|
|
return NetAddr{AddrNetwork: a.Network(), Addr: a.String()}
|
|
|
|
}
|
|
|
|
|
2019-03-21 02:51:32 +00:00
|
|
|
// JoinAddrSlices joins two addr slices and returns a resulting slice
|
|
|
|
func JoinAddrSlices(a []NetAddr, b []NetAddr) []NetAddr {
|
|
|
|
if len(a)+len(b) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
out := make([]NetAddr, 0, len(a)+len(b))
|
|
|
|
out = append(out, a...)
|
|
|
|
out = append(out, b...)
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2016-02-09 04:55:13 +00:00
|
|
|
// ParseHostPortAddr takes strings like "host:port" and returns
|
|
|
|
// *NetAddr or an error
|
|
|
|
//
|
|
|
|
// If defaultPort == -1 it expects 'hostport' string to have it
|
|
|
|
func ParseHostPortAddr(hostport string, defaultPort int) (*NetAddr, error) {
|
2018-08-02 00:25:16 +00:00
|
|
|
addr, err := ParseAddr(hostport)
|
2016-02-09 04:55:13 +00:00
|
|
|
if err != nil {
|
2018-08-02 00:25:16 +00:00
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
|
|
|
// port is required but not set
|
|
|
|
if defaultPort == -1 && addr.Addr == addr.Host() {
|
|
|
|
return nil, trace.BadParameter("missing port in address %q", hostport)
|
2016-02-09 04:55:13 +00:00
|
|
|
}
|
2019-07-19 17:55:11 +00:00
|
|
|
addr.Addr = net.JoinHostPort(addr.Host(), fmt.Sprintf("%v", addr.Port(defaultPort)))
|
2018-08-02 00:25:16 +00:00
|
|
|
return addr, nil
|
2016-02-09 04:55:13 +00:00
|
|
|
}
|
|
|
|
|
2018-06-18 00:53:02 +00:00
|
|
|
// DialAddrFromListenAddr returns dial address from listen address
|
|
|
|
func DialAddrFromListenAddr(listenAddr NetAddr) NetAddr {
|
|
|
|
if listenAddr.IsEmpty() {
|
|
|
|
return listenAddr
|
|
|
|
}
|
|
|
|
return NetAddr{Addr: ReplaceLocalhost(listenAddr.Addr, "127.0.0.1")}
|
|
|
|
}
|
|
|
|
|
2016-03-02 01:18:06 +00:00
|
|
|
// ReplaceLocalhost checks if a given address is link-local (like 0.0.0.0 or 127.0.0.1)
|
|
|
|
// and replaces it with the IP taken from replaceWith, preserving the original port
|
|
|
|
//
|
|
|
|
// Both addresses are in "host:port" format
|
|
|
|
// The function returns the original value if it encounters any problems with parsing
|
|
|
|
func ReplaceLocalhost(addr, replaceWith string) string {
|
|
|
|
host, port, err := net.SplitHostPort(addr)
|
|
|
|
if err != nil {
|
|
|
|
return addr
|
|
|
|
}
|
2016-03-24 01:08:17 +00:00
|
|
|
if IsLocalhost(host) {
|
2016-03-02 01:18:06 +00:00
|
|
|
host, _, err = net.SplitHostPort(replaceWith)
|
|
|
|
if err != nil {
|
|
|
|
return addr
|
|
|
|
}
|
|
|
|
addr = net.JoinHostPort(host, port)
|
|
|
|
}
|
|
|
|
return addr
|
|
|
|
}
|
2016-03-23 18:12:24 +00:00
|
|
|
|
2016-03-24 01:08:17 +00:00
|
|
|
// IsLocalhost returns true if this is a local hostname or ip
|
|
|
|
func IsLocalhost(host string) bool {
|
2016-03-23 18:12:24 +00:00
|
|
|
if host == "localhost" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
ip := net.ParseIP(host)
|
|
|
|
return ip.IsLoopback() || ip.IsUnspecified()
|
|
|
|
}
|
2016-03-24 01:08:17 +00:00
|
|
|
|
|
|
|
// IsLoopback returns 'true' if a given hostname resolves to local
|
|
|
|
// host's loopback interface
|
|
|
|
func IsLoopback(host string) bool {
|
2016-03-24 01:23:48 +00:00
|
|
|
if strings.Contains(host, ":") {
|
|
|
|
var err error
|
|
|
|
host, _, err = net.SplitHostPort(host)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2016-03-24 01:08:17 +00:00
|
|
|
ips, err := net.LookupIP(host)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for _, ip := range ips {
|
|
|
|
if ip.IsLoopback() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2016-05-13 07:32:39 +00:00
|
|
|
|
|
|
|
// GuessIP tries to guess an IP address this machine is reachable at on the
|
|
|
|
// internal network, always picking IPv4 from the internal address space
|
|
|
|
//
|
|
|
|
// If no internal IPs are found, it returns 127.0.0.1 but it never returns
|
|
|
|
// an address from the public IP space
|
|
|
|
func GuessHostIP() (ip net.IP, err error) {
|
|
|
|
ifaces, err := net.Interfaces()
|
|
|
|
if err != nil {
|
|
|
|
return nil, trace.Wrap(err)
|
|
|
|
}
|
2016-08-04 23:23:00 +00:00
|
|
|
adrs := make([]net.Addr, 0)
|
|
|
|
for _, iface := range ifaces {
|
|
|
|
ifadrs, err := iface.Addrs()
|
|
|
|
if err != nil {
|
|
|
|
log.Warn(err)
|
|
|
|
} else {
|
|
|
|
adrs = append(adrs, ifadrs...)
|
|
|
|
}
|
2016-06-24 16:43:50 +00:00
|
|
|
}
|
2016-08-04 23:23:00 +00:00
|
|
|
return guessHostIP(adrs), nil
|
2016-06-24 16:43:50 +00:00
|
|
|
}
|
|
|
|
|
2016-08-04 23:23:00 +00:00
|
|
|
func guessHostIP(addrs []net.Addr) (ip net.IP) {
|
2016-05-13 07:32:39 +00:00
|
|
|
// collect the list of all IPv4s
|
2016-06-24 16:43:50 +00:00
|
|
|
var ips []net.IP
|
2016-08-04 23:23:00 +00:00
|
|
|
for _, addr := range addrs {
|
|
|
|
var ipAddr net.IP
|
|
|
|
a, ok := addr.(*net.IPAddr)
|
|
|
|
if ok {
|
|
|
|
ipAddr = a.IP
|
|
|
|
} else {
|
|
|
|
in, ok := addr.(*net.IPNet)
|
2016-05-13 07:32:39 +00:00
|
|
|
if ok {
|
2016-08-04 23:23:00 +00:00
|
|
|
ipAddr = in.IP
|
2016-05-13 07:32:39 +00:00
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2016-08-04 23:23:00 +00:00
|
|
|
if ipAddr.To4() == nil || ipAddr.IsLoopback() || ipAddr.IsMulticast() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
ips = append(ips, ipAddr)
|
2016-05-13 07:32:39 +00:00
|
|
|
}
|
2018-11-15 23:24:36 +00:00
|
|
|
|
2016-05-13 07:32:39 +00:00
|
|
|
for i := range ips {
|
2018-11-15 23:24:36 +00:00
|
|
|
first := &net.IPNet{IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)}
|
|
|
|
second := &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)}
|
|
|
|
third := &net.IPNet{IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)}
|
|
|
|
|
|
|
|
// our first pick would be "10.0.0.0/8"
|
|
|
|
if first.Contains(ips[i]) {
|
|
|
|
ip = ips[i]
|
|
|
|
break
|
|
|
|
// our 2nd pick would be "192.168.0.0/16"
|
|
|
|
} else if second.Contains(ips[i]) {
|
|
|
|
ip = ips[i]
|
|
|
|
// our 3rd pick would be "172.16.0.0/12"
|
|
|
|
} else if third.Contains(ips[i]) && !second.Contains(ip) {
|
2016-05-13 07:32:39 +00:00
|
|
|
ip = ips[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ip == nil {
|
2016-06-24 21:39:50 +00:00
|
|
|
if len(ips) > 0 {
|
|
|
|
return ips[0]
|
|
|
|
}
|
|
|
|
// fallback to loopback
|
2016-05-13 07:32:39 +00:00
|
|
|
ip = net.IPv4(127, 0, 0, 1)
|
|
|
|
}
|
2016-06-24 16:43:50 +00:00
|
|
|
return ip
|
|
|
|
}
|