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