blob: 7ea2df226179009718a3130408fcaf01a87e93f1 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package net
18
19import (
20 "bufio"
21 "bytes"
22 "context"
23 "crypto/tls"
24 "fmt"
25 "io"
26 "net"
27 "net/http"
28 "net/url"
29 "os"
30 "path"
31 "strconv"
32 "strings"
33
34 "github.com/golang/glog"
35 "golang.org/x/net/http2"
36)
37
38// JoinPreservingTrailingSlash does a path.Join of the specified elements,
39// preserving any trailing slash on the last non-empty segment
40func JoinPreservingTrailingSlash(elem ...string) string {
41 // do the basic path join
42 result := path.Join(elem...)
43
44 // find the last non-empty segment
45 for i := len(elem) - 1; i >= 0; i-- {
46 if len(elem[i]) > 0 {
47 // if the last segment ended in a slash, ensure our result does as well
48 if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
49 result += "/"
50 }
51 break
52 }
53 }
54
55 return result
56}
57
58// IsProbableEOF returns true if the given error resembles a connection termination
59// scenario that would justify assuming that the watch is empty.
60// These errors are what the Go http stack returns back to us which are general
61// connection closure errors (strongly correlated) and callers that need to
62// differentiate probable errors in connection behavior between normal "this is
63// disconnected" should use the method.
64func IsProbableEOF(err error) bool {
65 if err == nil {
66 return false
67 }
68 if uerr, ok := err.(*url.Error); ok {
69 err = uerr.Err
70 }
71 switch {
72 case err == io.EOF:
73 return true
74 case err.Error() == "http: can't write HTTP request on broken connection":
75 return true
76 case strings.Contains(err.Error(), "connection reset by peer"):
77 return true
78 case strings.Contains(strings.ToLower(err.Error()), "use of closed network connection"):
79 return true
80 }
81 return false
82}
83
84var defaultTransport = http.DefaultTransport.(*http.Transport)
85
86// SetOldTransportDefaults applies the defaults from http.DefaultTransport
87// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
88func SetOldTransportDefaults(t *http.Transport) *http.Transport {
89 if t.Proxy == nil || isDefault(t.Proxy) {
90 // http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings
91 // ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY
92 t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
93 }
94 if t.DialContext == nil {
95 t.DialContext = defaultTransport.DialContext
96 }
97 if t.TLSHandshakeTimeout == 0 {
98 t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
99 }
100 return t
101}
102
103// SetTransportDefaults applies the defaults from http.DefaultTransport
104// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
105func SetTransportDefaults(t *http.Transport) *http.Transport {
106 t = SetOldTransportDefaults(t)
107 // Allow clients to disable http2 if needed.
108 if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
109 glog.Infof("HTTP2 has been explicitly disabled")
110 } else {
111 if err := http2.ConfigureTransport(t); err != nil {
112 glog.Warningf("Transport failed http2 configuration: %v", err)
113 }
114 }
115 return t
116}
117
118type RoundTripperWrapper interface {
119 http.RoundTripper
120 WrappedRoundTripper() http.RoundTripper
121}
122
123type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error)
124
125func DialerFor(transport http.RoundTripper) (DialFunc, error) {
126 if transport == nil {
127 return nil, nil
128 }
129
130 switch transport := transport.(type) {
131 case *http.Transport:
132 return transport.DialContext, nil
133 case RoundTripperWrapper:
134 return DialerFor(transport.WrappedRoundTripper())
135 default:
136 return nil, fmt.Errorf("unknown transport type: %T", transport)
137 }
138}
139
140type TLSClientConfigHolder interface {
141 TLSClientConfig() *tls.Config
142}
143
144func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) {
145 if transport == nil {
146 return nil, nil
147 }
148
149 switch transport := transport.(type) {
150 case *http.Transport:
151 return transport.TLSClientConfig, nil
152 case TLSClientConfigHolder:
153 return transport.TLSClientConfig(), nil
154 case RoundTripperWrapper:
155 return TLSClientConfig(transport.WrappedRoundTripper())
156 default:
157 return nil, fmt.Errorf("unknown transport type: %T", transport)
158 }
159}
160
161func FormatURL(scheme string, host string, port int, path string) *url.URL {
162 return &url.URL{
163 Scheme: scheme,
164 Host: net.JoinHostPort(host, strconv.Itoa(port)),
165 Path: path,
166 }
167}
168
169func GetHTTPClient(req *http.Request) string {
170 if userAgent, ok := req.Header["User-Agent"]; ok {
171 if len(userAgent) > 0 {
172 return userAgent[0]
173 }
174 }
175 return "unknown"
176}
177
178// SourceIPs splits the comma separated X-Forwarded-For header or returns the X-Real-Ip header or req.RemoteAddr,
179// in that order, ignoring invalid IPs. It returns nil if all of these are empty or invalid.
180func SourceIPs(req *http.Request) []net.IP {
181 hdr := req.Header
182 // First check the X-Forwarded-For header for requests via proxy.
183 hdrForwardedFor := hdr.Get("X-Forwarded-For")
184 forwardedForIPs := []net.IP{}
185 if hdrForwardedFor != "" {
186 // X-Forwarded-For can be a csv of IPs in case of multiple proxies.
187 // Use the first valid one.
188 parts := strings.Split(hdrForwardedFor, ",")
189 for _, part := range parts {
190 ip := net.ParseIP(strings.TrimSpace(part))
191 if ip != nil {
192 forwardedForIPs = append(forwardedForIPs, ip)
193 }
194 }
195 }
196 if len(forwardedForIPs) > 0 {
197 return forwardedForIPs
198 }
199
200 // Try the X-Real-Ip header.
201 hdrRealIp := hdr.Get("X-Real-Ip")
202 if hdrRealIp != "" {
203 ip := net.ParseIP(hdrRealIp)
204 if ip != nil {
205 return []net.IP{ip}
206 }
207 }
208
209 // Fallback to Remote Address in request, which will give the correct client IP when there is no proxy.
210 // Remote Address in Go's HTTP server is in the form host:port so we need to split that first.
211 host, _, err := net.SplitHostPort(req.RemoteAddr)
212 if err == nil {
213 if remoteIP := net.ParseIP(host); remoteIP != nil {
214 return []net.IP{remoteIP}
215 }
216 }
217
218 // Fallback if Remote Address was just IP.
219 if remoteIP := net.ParseIP(req.RemoteAddr); remoteIP != nil {
220 return []net.IP{remoteIP}
221 }
222
223 return nil
224}
225
226// Extracts and returns the clients IP from the given request.
227// Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
228// Returns nil if none of them are set or is set to an invalid value.
229func GetClientIP(req *http.Request) net.IP {
230 ips := SourceIPs(req)
231 if len(ips) == 0 {
232 return nil
233 }
234 return ips[0]
235}
236
237// Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's
238// IP address to the X-Forwarded-For chain.
239func AppendForwardedForHeader(req *http.Request) {
240 // Copied from net/http/httputil/reverseproxy.go:
241 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
242 // If we aren't the first proxy retain prior
243 // X-Forwarded-For information as a comma+space
244 // separated list and fold multiple headers into one.
245 if prior, ok := req.Header["X-Forwarded-For"]; ok {
246 clientIP = strings.Join(prior, ", ") + ", " + clientIP
247 }
248 req.Header.Set("X-Forwarded-For", clientIP)
249 }
250}
251
252var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment)
253
254// isDefault checks to see if the transportProxierFunc is pointing to the default one
255func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool {
256 transportProxierPointer := fmt.Sprintf("%p", transportProxier)
257 return transportProxierPointer == defaultProxyFuncPointer
258}
259
260// NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if
261// no matching CIDRs are found
262func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) {
263 // we wrap the default method, so we only need to perform our check if the NO_PROXY (or no_proxy) envvar has a CIDR in it
264 noProxyEnv := os.Getenv("NO_PROXY")
265 if noProxyEnv == "" {
266 noProxyEnv = os.Getenv("no_proxy")
267 }
268 noProxyRules := strings.Split(noProxyEnv, ",")
269
270 cidrs := []*net.IPNet{}
271 for _, noProxyRule := range noProxyRules {
272 _, cidr, _ := net.ParseCIDR(noProxyRule)
273 if cidr != nil {
274 cidrs = append(cidrs, cidr)
275 }
276 }
277
278 if len(cidrs) == 0 {
279 return delegate
280 }
281
282 return func(req *http.Request) (*url.URL, error) {
283 ip := net.ParseIP(req.URL.Hostname())
284 if ip == nil {
285 return delegate(req)
286 }
287
288 for _, cidr := range cidrs {
289 if cidr.Contains(ip) {
290 return nil, nil
291 }
292 }
293
294 return delegate(req)
295 }
296}
297
298// DialerFunc implements Dialer for the provided function.
299type DialerFunc func(req *http.Request) (net.Conn, error)
300
301func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) {
302 return fn(req)
303}
304
305// Dialer dials a host and writes a request to it.
306type Dialer interface {
307 // Dial connects to the host specified by req's URL, writes the request to the connection, and
308 // returns the opened net.Conn.
309 Dial(req *http.Request) (net.Conn, error)
310}
311
312// ConnectWithRedirects uses dialer to send req, following up to 10 redirects (relative to
313// originalLocation). It returns the opened net.Conn and the raw response bytes.
314func ConnectWithRedirects(originalMethod string, originalLocation *url.URL, header http.Header, originalBody io.Reader, dialer Dialer) (net.Conn, []byte, error) {
315 const (
316 maxRedirects = 10
317 maxResponseSize = 16384 // play it safe to allow the potential for lots of / large headers
318 )
319
320 var (
321 location = originalLocation
322 method = originalMethod
323 intermediateConn net.Conn
324 rawResponse = bytes.NewBuffer(make([]byte, 0, 256))
325 body = originalBody
326 )
327
328 defer func() {
329 if intermediateConn != nil {
330 intermediateConn.Close()
331 }
332 }()
333
334redirectLoop:
335 for redirects := 0; ; redirects++ {
336 if redirects > maxRedirects {
337 return nil, nil, fmt.Errorf("too many redirects (%d)", redirects)
338 }
339
340 req, err := http.NewRequest(method, location.String(), body)
341 if err != nil {
342 return nil, nil, err
343 }
344
345 req.Header = header
346
347 intermediateConn, err = dialer.Dial(req)
348 if err != nil {
349 return nil, nil, err
350 }
351
352 // Peek at the backend response.
353 rawResponse.Reset()
354 respReader := bufio.NewReader(io.TeeReader(
355 io.LimitReader(intermediateConn, maxResponseSize), // Don't read more than maxResponseSize bytes.
356 rawResponse)) // Save the raw response.
357 resp, err := http.ReadResponse(respReader, nil)
358 if err != nil {
359 // Unable to read the backend response; let the client handle it.
360 glog.Warningf("Error reading backend response: %v", err)
361 break redirectLoop
362 }
363
364 switch resp.StatusCode {
365 case http.StatusFound:
366 // Redirect, continue.
367 default:
368 // Don't redirect.
369 break redirectLoop
370 }
371
372 // Redirected requests switch to "GET" according to the HTTP spec:
373 // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3
374 method = "GET"
375 // don't send a body when following redirects
376 body = nil
377
378 resp.Body.Close() // not used
379
380 // Reset the connection.
381 intermediateConn.Close()
382 intermediateConn = nil
383
384 // Prepare to follow the redirect.
385 redirectStr := resp.Header.Get("Location")
386 if redirectStr == "" {
387 return nil, nil, fmt.Errorf("%d response missing Location header", resp.StatusCode)
388 }
389 // We have to parse relative to the current location, NOT originalLocation. For example,
390 // if we request http://foo.com/a and get back "http://bar.com/b", the result should be
391 // http://bar.com/b. If we then make that request and get back a redirect to "/c", the result
392 // should be http://bar.com/c, not http://foo.com/c.
393 location, err = location.Parse(redirectStr)
394 if err != nil {
395 return nil, nil, fmt.Errorf("malformed Location header: %v", err)
396 }
397 }
398
399 connToReturn := intermediateConn
400 intermediateConn = nil // Don't close the connection when we return it.
401 return connToReturn, rawResponse.Bytes(), nil
402}
403
404// CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
405func CloneRequest(req *http.Request) *http.Request {
406 r := new(http.Request)
407
408 // shallow clone
409 *r = *req
410
411 // deep copy headers
412 r.Header = CloneHeader(req.Header)
413
414 return r
415}
416
417// CloneHeader creates a deep copy of an http.Header.
418func CloneHeader(in http.Header) http.Header {
419 out := make(http.Header, len(in))
420 for key, values := range in {
421 newValues := make([]string, len(values))
422 copy(newValues, values)
423 out[key] = newValues
424 }
425 return out
426}