go.etcd.io/etcd@v3.3.27+incompatible/pkg/netutil/netutil.go (about)

     1  // Copyright 2015 The etcd 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  
    15  // Package netutil implements network-related utility functions.
    16  package netutil
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"net"
    22  	"net/url"
    23  	"reflect"
    24  	"sort"
    25  	"time"
    26  
    27  	"github.com/coreos/etcd/pkg/types"
    28  	"github.com/coreos/pkg/capnslog"
    29  )
    30  
    31  var (
    32  	plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "pkg/netutil")
    33  
    34  	// indirection for testing
    35  	resolveTCPAddr = resolveTCPAddrDefault
    36  )
    37  
    38  const retryInterval = time.Second
    39  
    40  // taken from go's ResolveTCP code but uses configurable ctx
    41  func resolveTCPAddrDefault(ctx context.Context, addr string) (*net.TCPAddr, error) {
    42  	host, port, serr := net.SplitHostPort(addr)
    43  	if serr != nil {
    44  		return nil, serr
    45  	}
    46  	portnum, perr := net.DefaultResolver.LookupPort(ctx, "tcp", port)
    47  	if perr != nil {
    48  		return nil, perr
    49  	}
    50  
    51  	var ips []net.IPAddr
    52  	if ip := net.ParseIP(host); ip != nil {
    53  		ips = []net.IPAddr{{IP: ip}}
    54  	} else {
    55  		// Try as a DNS name.
    56  		ipss, err := net.DefaultResolver.LookupIPAddr(ctx, host)
    57  		if err != nil {
    58  			return nil, err
    59  		}
    60  		ips = ipss
    61  	}
    62  	// randomize?
    63  	ip := ips[0]
    64  	return &net.TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}, nil
    65  }
    66  
    67  // resolveTCPAddrs is a convenience wrapper for net.ResolveTCPAddr.
    68  // resolveTCPAddrs return a new set of url.URLs, in which all DNS hostnames
    69  // are resolved.
    70  func resolveTCPAddrs(ctx context.Context, urls [][]url.URL) ([][]url.URL, error) {
    71  	newurls := make([][]url.URL, 0)
    72  	for _, us := range urls {
    73  		nus := make([]url.URL, len(us))
    74  		for i, u := range us {
    75  			nu, err := url.Parse(u.String())
    76  			if err != nil {
    77  				return nil, fmt.Errorf("failed to parse %q (%v)", u.String(), err)
    78  			}
    79  			nus[i] = *nu
    80  		}
    81  		for i, u := range nus {
    82  			h, err := resolveURL(ctx, u)
    83  			if err != nil {
    84  				return nil, fmt.Errorf("failed to resolve %q (%v)", u.String(), err)
    85  			}
    86  			if h != "" {
    87  				nus[i].Host = h
    88  			}
    89  		}
    90  		newurls = append(newurls, nus)
    91  	}
    92  	return newurls, nil
    93  }
    94  
    95  func resolveURL(ctx context.Context, u url.URL) (string, error) {
    96  	if u.Scheme == "unix" || u.Scheme == "unixs" {
    97  		// unix sockets don't resolve over TCP
    98  		return "", nil
    99  	}
   100  	host, _, err := net.SplitHostPort(u.Host)
   101  	if err != nil {
   102  		plog.Errorf("could not parse url %s during tcp resolving", u.Host)
   103  		return "", err
   104  	}
   105  	if host == "localhost" || net.ParseIP(host) != nil {
   106  		return "", nil
   107  	}
   108  	for ctx.Err() == nil {
   109  		tcpAddr, err := resolveTCPAddr(ctx, u.Host)
   110  		if err == nil {
   111  			plog.Infof("resolving %s to %s", u.Host, tcpAddr.String())
   112  			return tcpAddr.String(), nil
   113  		}
   114  		plog.Warningf("failed resolving host %s (%v); retrying in %v", u.Host, err, retryInterval)
   115  		select {
   116  		case <-ctx.Done():
   117  			plog.Errorf("could not resolve host %s", u.Host)
   118  			return "", err
   119  		case <-time.After(retryInterval):
   120  		}
   121  	}
   122  	return "", ctx.Err()
   123  }
   124  
   125  // urlsEqual checks equality of url.URLS between two arrays.
   126  // This check pass even if an URL is in hostname and opposite is in IP address.
   127  func urlsEqual(ctx context.Context, a []url.URL, b []url.URL) (bool, error) {
   128  	if len(a) != len(b) {
   129  		return false, fmt.Errorf("len(%q) != len(%q)", urlsToStrings(a), urlsToStrings(b))
   130  	}
   131  	urls, err := resolveTCPAddrs(ctx, [][]url.URL{a, b})
   132  	if err != nil {
   133  		return false, err
   134  	}
   135  	preva, prevb := a, b
   136  	a, b = urls[0], urls[1]
   137  	sort.Sort(types.URLs(a))
   138  	sort.Sort(types.URLs(b))
   139  	for i := range a {
   140  		if !reflect.DeepEqual(a[i], b[i]) {
   141  			return false, fmt.Errorf("%q(resolved from %q) != %q(resolved from %q)",
   142  				a[i].String(), preva[i].String(),
   143  				b[i].String(), prevb[i].String(),
   144  			)
   145  		}
   146  	}
   147  	return true, nil
   148  }
   149  
   150  // URLStringsEqual returns "true" if given URLs are valid
   151  // and resolved to same IP addresses. Otherwise, return "false"
   152  // and error, if any.
   153  func URLStringsEqual(ctx context.Context, a []string, b []string) (bool, error) {
   154  	if len(a) != len(b) {
   155  		return false, fmt.Errorf("len(%q) != len(%q)", a, b)
   156  	}
   157  	urlsA := make([]url.URL, 0)
   158  	for _, str := range a {
   159  		u, err := url.Parse(str)
   160  		if err != nil {
   161  			return false, fmt.Errorf("failed to parse %q", str)
   162  		}
   163  		urlsA = append(urlsA, *u)
   164  	}
   165  	urlsB := make([]url.URL, 0)
   166  	for _, str := range b {
   167  		u, err := url.Parse(str)
   168  		if err != nil {
   169  			return false, fmt.Errorf("failed to parse %q", str)
   170  		}
   171  		urlsB = append(urlsB, *u)
   172  	}
   173  	return urlsEqual(ctx, urlsA, urlsB)
   174  }
   175  
   176  func urlsToStrings(us []url.URL) []string {
   177  	rs := make([]string, len(us))
   178  	for i := range us {
   179  		rs[i] = us[i].String()
   180  	}
   181  	return rs
   182  }
   183  
   184  func IsNetworkTimeoutError(err error) bool {
   185  	nerr, ok := err.(net.Error)
   186  	return ok && nerr.Timeout()
   187  }