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 }