1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package netutil
16
17 import (
18 "context"
19 "fmt"
20 "net"
21 "net/url"
22 "reflect"
23 "sort"
24 "time"
25
26 "go.etcd.io/etcd/client/pkg/v3/types"
27
28 "go.uber.org/zap"
29 )
30
31
32 var resolveTCPAddr = resolveTCPAddrDefault
33
34 const retryInterval = time.Second
35
36
37 func 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
52 ipss, err := net.DefaultResolver.LookupIPAddr(ctx, host)
53 if err != nil {
54 return nil, err
55 }
56 ips = ipss
57 }
58
59 ip := ips[0]
60 return &net.TCPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}, nil
61 }
62
63
64
65
66 func 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
91 func resolveURL(ctx context.Context, lg *zap.Logger, u url.URL) (string, error) {
92 if u.Scheme == "unix" || u.Scheme == "unixs" {
93
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" {
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
146
147 func 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
152 sort.Sort(types.URLs(a))
153 sort.Sort(types.URLs(b))
154 var needResolve bool
155 for i := range a {
156 if !reflect.DeepEqual(a[i], b[i]) {
157 needResolve = true
158 break
159 }
160 }
161 if !needResolve {
162 return true, nil
163 }
164
165
166 urls, err := resolveTCPAddrs(ctx, lg, [][]url.URL{a, b})
167 if err != nil {
168 return false, err
169 }
170 a, b = urls[0], urls[1]
171 sort.Sort(types.URLs(a))
172 sort.Sort(types.URLs(b))
173 for i := range a {
174 if !reflect.DeepEqual(a[i], b[i]) {
175 return false, fmt.Errorf("resolved urls: %q != %q", a[i].String(), b[i].String())
176 }
177 }
178 return true, nil
179 }
180
181
182
183
184 func URLStringsEqual(ctx context.Context, lg *zap.Logger, a []string, b []string) (bool, error) {
185 if len(a) != len(b) {
186 return false, fmt.Errorf("len(%q) != len(%q)", a, b)
187 }
188 urlsA, err := stringsToURLs(a)
189 if err != nil {
190 return false, err
191 }
192 urlsB, err := stringsToURLs(b)
193 if err != nil {
194 return false, err
195 }
196 return urlsEqual(ctx, lg, urlsA, urlsB)
197 }
198
199 func urlsToStrings(us []url.URL) []string {
200 rs := make([]string, len(us))
201 for i := range us {
202 rs[i] = us[i].String()
203 }
204 return rs
205 }
206
207 func stringsToURLs(us []string) ([]url.URL, error) {
208 urls := make([]url.URL, 0, len(us))
209 for _, str := range us {
210 u, err := url.Parse(str)
211 if err != nil {
212 return nil, fmt.Errorf("failed to parse string to URL: %q", str)
213 }
214 urls = append(urls, *u)
215 }
216 return urls, nil
217 }
218
219 func IsNetworkTimeoutError(err error) bool {
220 nerr, ok := err.(net.Error)
221 return ok && nerr.Timeout()
222 }
223
View as plain text