263 lines
7.9 KiB
Go
263 lines
7.9 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package net
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/golang/glog"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
// IsProbableEOF returns true if the given error resembles a connection termination
|
|
// scenario that would justify assuming that the watch is empty.
|
|
// These errors are what the Go http stack returns back to us which are general
|
|
// connection closure errors (strongly correlated) and callers that need to
|
|
// differentiate probable errors in connection behavior between normal "this is
|
|
// disconnected" should use the method.
|
|
func IsProbableEOF(err error) bool {
|
|
if uerr, ok := err.(*url.Error); ok {
|
|
err = uerr.Err
|
|
}
|
|
switch {
|
|
case err == io.EOF:
|
|
return true
|
|
case err.Error() == "http: can't write HTTP request on broken connection":
|
|
return true
|
|
case strings.Contains(err.Error(), "connection reset by peer"):
|
|
return true
|
|
case strings.Contains(strings.ToLower(err.Error()), "use of closed network connection"):
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
var defaultTransport = http.DefaultTransport.(*http.Transport)
|
|
|
|
// SetOldTransportDefaults applies the defaults from http.DefaultTransport
|
|
// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
|
|
func SetOldTransportDefaults(t *http.Transport) *http.Transport {
|
|
if t.Proxy == nil || isDefault(t.Proxy) {
|
|
// http.ProxyFromEnvironment doesn't respect CIDRs and that makes it impossible to exclude things like pod and service IPs from proxy settings
|
|
// ProxierWithNoProxyCIDR allows CIDR rules in NO_PROXY
|
|
t.Proxy = NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
|
|
}
|
|
if t.Dial == nil {
|
|
t.Dial = defaultTransport.Dial
|
|
}
|
|
if t.TLSHandshakeTimeout == 0 {
|
|
t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
|
|
}
|
|
return t
|
|
}
|
|
|
|
// SetTransportDefaults applies the defaults from http.DefaultTransport
|
|
// for the Proxy, Dial, and TLSHandshakeTimeout fields if unset
|
|
func SetTransportDefaults(t *http.Transport) *http.Transport {
|
|
t = SetOldTransportDefaults(t)
|
|
// Allow clients to disable http2 if needed.
|
|
if s := os.Getenv("DISABLE_HTTP2"); len(s) > 0 {
|
|
glog.Infof("HTTP2 has been explicitly disabled")
|
|
} else {
|
|
if err := http2.ConfigureTransport(t); err != nil {
|
|
glog.Warningf("Transport failed http2 configuration: %v", err)
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
type RoundTripperWrapper interface {
|
|
http.RoundTripper
|
|
WrappedRoundTripper() http.RoundTripper
|
|
}
|
|
|
|
type DialFunc func(net, addr string) (net.Conn, error)
|
|
|
|
func Dialer(transport http.RoundTripper) (DialFunc, error) {
|
|
if transport == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
switch transport := transport.(type) {
|
|
case *http.Transport:
|
|
return transport.Dial, nil
|
|
case RoundTripperWrapper:
|
|
return Dialer(transport.WrappedRoundTripper())
|
|
default:
|
|
return nil, fmt.Errorf("unknown transport type: %v", transport)
|
|
}
|
|
}
|
|
|
|
// CloneTLSConfig returns a tls.Config with all exported fields except SessionTicketsDisabled and SessionTicketKey copied.
|
|
// This makes it safe to call CloneTLSConfig on a config in active use by a server.
|
|
// TODO: replace with tls.Config#Clone when we move to go1.8
|
|
func CloneTLSConfig(cfg *tls.Config) *tls.Config {
|
|
if cfg == nil {
|
|
return &tls.Config{}
|
|
}
|
|
return &tls.Config{
|
|
Rand: cfg.Rand,
|
|
Time: cfg.Time,
|
|
Certificates: cfg.Certificates,
|
|
NameToCertificate: cfg.NameToCertificate,
|
|
GetCertificate: cfg.GetCertificate,
|
|
RootCAs: cfg.RootCAs,
|
|
NextProtos: cfg.NextProtos,
|
|
ServerName: cfg.ServerName,
|
|
ClientAuth: cfg.ClientAuth,
|
|
ClientCAs: cfg.ClientCAs,
|
|
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
|
CipherSuites: cfg.CipherSuites,
|
|
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
|
|
ClientSessionCache: cfg.ClientSessionCache,
|
|
MinVersion: cfg.MinVersion,
|
|
MaxVersion: cfg.MaxVersion,
|
|
CurvePreferences: cfg.CurvePreferences,
|
|
}
|
|
}
|
|
|
|
func TLSClientConfig(transport http.RoundTripper) (*tls.Config, error) {
|
|
if transport == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
switch transport := transport.(type) {
|
|
case *http.Transport:
|
|
return transport.TLSClientConfig, nil
|
|
case RoundTripperWrapper:
|
|
return TLSClientConfig(transport.WrappedRoundTripper())
|
|
default:
|
|
return nil, fmt.Errorf("unknown transport type: %v", transport)
|
|
}
|
|
}
|
|
|
|
func FormatURL(scheme string, host string, port int, path string) *url.URL {
|
|
return &url.URL{
|
|
Scheme: scheme,
|
|
Host: net.JoinHostPort(host, strconv.Itoa(port)),
|
|
Path: path,
|
|
}
|
|
}
|
|
|
|
func GetHTTPClient(req *http.Request) string {
|
|
if userAgent, ok := req.Header["User-Agent"]; ok {
|
|
if len(userAgent) > 0 {
|
|
return userAgent[0]
|
|
}
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
// Extracts and returns the clients IP from the given request.
|
|
// Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
|
|
// Returns nil if none of them are set or is set to an invalid value.
|
|
func GetClientIP(req *http.Request) net.IP {
|
|
hdr := req.Header
|
|
// First check the X-Forwarded-For header for requests via proxy.
|
|
hdrForwardedFor := hdr.Get("X-Forwarded-For")
|
|
if hdrForwardedFor != "" {
|
|
// X-Forwarded-For can be a csv of IPs in case of multiple proxies.
|
|
// Use the first valid one.
|
|
parts := strings.Split(hdrForwardedFor, ",")
|
|
for _, part := range parts {
|
|
ip := net.ParseIP(strings.TrimSpace(part))
|
|
if ip != nil {
|
|
return ip
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try the X-Real-Ip header.
|
|
hdrRealIp := hdr.Get("X-Real-Ip")
|
|
if hdrRealIp != "" {
|
|
ip := net.ParseIP(hdrRealIp)
|
|
if ip != nil {
|
|
return ip
|
|
}
|
|
}
|
|
|
|
// Fallback to Remote Address in request, which will give the correct client IP when there is no proxy.
|
|
// Remote Address in Go's HTTP server is in the form host:port so we need to split that first.
|
|
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
if err == nil {
|
|
return net.ParseIP(host)
|
|
}
|
|
|
|
// Fallback if Remote Address was just IP.
|
|
return net.ParseIP(req.RemoteAddr)
|
|
}
|
|
|
|
var defaultProxyFuncPointer = fmt.Sprintf("%p", http.ProxyFromEnvironment)
|
|
|
|
// isDefault checks to see if the transportProxierFunc is pointing to the default one
|
|
func isDefault(transportProxier func(*http.Request) (*url.URL, error)) bool {
|
|
transportProxierPointer := fmt.Sprintf("%p", transportProxier)
|
|
return transportProxierPointer == defaultProxyFuncPointer
|
|
}
|
|
|
|
// NewProxierWithNoProxyCIDR constructs a Proxier function that respects CIDRs in NO_PROXY and delegates if
|
|
// no matching CIDRs are found
|
|
func NewProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) {
|
|
// we wrap the default method, so we only need to perform our check if the NO_PROXY envvar has a CIDR in it
|
|
noProxyEnv := os.Getenv("NO_PROXY")
|
|
noProxyRules := strings.Split(noProxyEnv, ",")
|
|
|
|
cidrs := []*net.IPNet{}
|
|
for _, noProxyRule := range noProxyRules {
|
|
_, cidr, _ := net.ParseCIDR(noProxyRule)
|
|
if cidr != nil {
|
|
cidrs = append(cidrs, cidr)
|
|
}
|
|
}
|
|
|
|
if len(cidrs) == 0 {
|
|
return delegate
|
|
}
|
|
|
|
return func(req *http.Request) (*url.URL, error) {
|
|
host := req.URL.Host
|
|
// for some urls, the Host is already the host, not the host:port
|
|
if net.ParseIP(host) == nil {
|
|
var err error
|
|
host, _, err = net.SplitHostPort(req.URL.Host)
|
|
if err != nil {
|
|
return delegate(req)
|
|
}
|
|
}
|
|
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return delegate(req)
|
|
}
|
|
|
|
for _, cidr := range cidrs {
|
|
if cidr.Contains(ip) {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
return delegate(req)
|
|
}
|
|
}
|