rebase: update to latest github.com/openshift/api version

Also vendor all dependencies.

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2024-01-16 14:00:59 +01:00
committed by mergify[bot]
parent ab87045afb
commit ce603fb47e
547 changed files with 335074 additions and 4 deletions

699
api/vendor/k8s.io/apimachinery/pkg/util/net/http.go generated vendored Normal file
View File

@ -0,0 +1,699 @@
/*
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 (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"mime"
"net"
"net/http"
"net/url"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
"golang.org/x/net/http2"
"k8s.io/klog/v2"
netutils "k8s.io/utils/net"
)
// JoinPreservingTrailingSlash does a path.Join of the specified elements,
// preserving any trailing slash on the last non-empty segment
func JoinPreservingTrailingSlash(elem ...string) string {
// do the basic path join
result := path.Join(elem...)
// find the last non-empty segment
for i := len(elem) - 1; i >= 0; i-- {
if len(elem[i]) > 0 {
// if the last segment ended in a slash, ensure our result does as well
if strings.HasSuffix(elem[i], "/") && !strings.HasSuffix(result, "/") {
result += "/"
}
break
}
}
return result
}
// IsTimeout returns true if the given error is a network timeout error
func IsTimeout(err error) bool {
var neterr net.Error
if errors.As(err, &neterr) {
return neterr != nil && neterr.Timeout()
}
return false
}
// 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 err == nil {
return false
}
var uerr *url.Error
if errors.As(err, &uerr) {
err = uerr.Err
}
msg := err.Error()
switch {
case err == io.EOF:
return true
case err == io.ErrUnexpectedEOF:
return true
case msg == "http: can't write HTTP request on broken connection":
return true
case strings.Contains(msg, "http2: server sent GOAWAY and closed the connection"):
return true
case strings.Contains(msg, "connection reset by peer"):
return true
case strings.Contains(strings.ToLower(msg), "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 no custom dialer is set, use the default context dialer
//lint:file-ignore SA1019 Keep supporting deprecated Dial method of custom transports
if t.DialContext == nil && t.Dial == nil {
t.DialContext = defaultTransport.DialContext
}
if t.TLSHandshakeTimeout == 0 {
t.TLSHandshakeTimeout = defaultTransport.TLSHandshakeTimeout
}
if t.IdleConnTimeout == 0 {
t.IdleConnTimeout = defaultTransport.IdleConnTimeout
}
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 {
klog.Info("HTTP2 has been explicitly disabled")
} else if allowsHTTP2(t) {
if err := configureHTTP2Transport(t); err != nil {
klog.Warningf("Transport failed http2 configuration: %v", err)
}
}
return t
}
func readIdleTimeoutSeconds() int {
ret := 30
// User can set the readIdleTimeout to 0 to disable the HTTP/2
// connection health check.
if s := os.Getenv("HTTP2_READ_IDLE_TIMEOUT_SECONDS"); len(s) > 0 {
i, err := strconv.Atoi(s)
if err != nil {
klog.Warningf("Illegal HTTP2_READ_IDLE_TIMEOUT_SECONDS(%q): %v."+
" Default value %d is used", s, err, ret)
return ret
}
ret = i
}
return ret
}
func pingTimeoutSeconds() int {
ret := 15
if s := os.Getenv("HTTP2_PING_TIMEOUT_SECONDS"); len(s) > 0 {
i, err := strconv.Atoi(s)
if err != nil {
klog.Warningf("Illegal HTTP2_PING_TIMEOUT_SECONDS(%q): %v."+
" Default value %d is used", s, err, ret)
return ret
}
ret = i
}
return ret
}
func configureHTTP2Transport(t *http.Transport) error {
t2, err := http2.ConfigureTransports(t)
if err != nil {
return err
}
// The following enables the HTTP/2 connection health check added in
// https://github.com/golang/net/pull/55. The health check detects and
// closes broken transport layer connections. Without the health check,
// a broken connection can linger too long, e.g., a broken TCP
// connection will be closed by the Linux kernel after 13 to 30 minutes
// by default, which caused
// https://github.com/kubernetes/client-go/issues/374 and
// https://github.com/kubernetes/kubernetes/issues/87615.
t2.ReadIdleTimeout = time.Duration(readIdleTimeoutSeconds()) * time.Second
t2.PingTimeout = time.Duration(pingTimeoutSeconds()) * time.Second
return nil
}
func allowsHTTP2(t *http.Transport) bool {
if t.TLSClientConfig == nil || len(t.TLSClientConfig.NextProtos) == 0 {
// the transport expressed no NextProto preference, allow
return true
}
for _, p := range t.TLSClientConfig.NextProtos {
if p == http2.NextProtoTLS {
// the transport explicitly allowed http/2
return true
}
}
// the transport explicitly set NextProtos and excluded http/2
return false
}
type RoundTripperWrapper interface {
http.RoundTripper
WrappedRoundTripper() http.RoundTripper
}
type DialFunc func(ctx context.Context, net, addr string) (net.Conn, error)
func DialerFor(transport http.RoundTripper) (DialFunc, error) {
if transport == nil {
return nil, nil
}
switch transport := transport.(type) {
case *http.Transport:
// transport.DialContext takes precedence over transport.Dial
if transport.DialContext != nil {
return transport.DialContext, nil
}
// adapt transport.Dial to the DialWithContext signature
if transport.Dial != nil {
return func(ctx context.Context, net, addr string) (net.Conn, error) {
return transport.Dial(net, addr)
}, nil
}
// otherwise return nil
return nil, nil
case RoundTripperWrapper:
return DialerFor(transport.WrappedRoundTripper())
default:
return nil, fmt.Errorf("unknown transport type: %T", transport)
}
}
// CloseIdleConnectionsFor close idles connections for the Transport.
// If the Transport is wrapped it iterates over the wrapped round trippers
// until it finds one that implements the CloseIdleConnections method.
// If the Transport does not have a CloseIdleConnections method
// then this function does nothing.
func CloseIdleConnectionsFor(transport http.RoundTripper) {
if transport == nil {
return
}
type closeIdler interface {
CloseIdleConnections()
}
switch transport := transport.(type) {
case closeIdler:
transport.CloseIdleConnections()
case RoundTripperWrapper:
CloseIdleConnectionsFor(transport.WrappedRoundTripper())
default:
klog.Warningf("unknown transport type: %T", transport)
}
}
type TLSClientConfigHolder interface {
TLSClientConfig() *tls.Config
}
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 TLSClientConfigHolder:
return transport.TLSClientConfig(), nil
case RoundTripperWrapper:
return TLSClientConfig(transport.WrappedRoundTripper())
default:
return nil, fmt.Errorf("unknown transport type: %T", 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 ua := req.UserAgent(); len(ua) != 0 {
return ua
}
return "unknown"
}
// SourceIPs splits the comma separated X-Forwarded-For header and joins it with
// the X-Real-Ip header and/or req.RemoteAddr, ignoring invalid IPs.
// The X-Real-Ip is omitted if it's already present in the X-Forwarded-For chain.
// The req.RemoteAddr is always the last IP in the returned list.
// It returns nil if all of these are empty or invalid.
func SourceIPs(req *http.Request) []net.IP {
var srcIPs []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 := netutils.ParseIPSloppy(strings.TrimSpace(part))
if ip != nil {
srcIPs = append(srcIPs, ip)
}
}
}
// Try the X-Real-Ip header.
hdrRealIp := hdr.Get("X-Real-Ip")
if hdrRealIp != "" {
ip := netutils.ParseIPSloppy(hdrRealIp)
// Only append the X-Real-Ip if it's not already contained in the X-Forwarded-For chain.
if ip != nil && !containsIP(srcIPs, ip) {
srcIPs = append(srcIPs, ip)
}
}
// Always include the request Remote Address as it cannot be easily spoofed.
var remoteIP net.IP
// 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 {
remoteIP = netutils.ParseIPSloppy(host)
}
// Fallback if Remote Address was just IP.
if remoteIP == nil {
remoteIP = netutils.ParseIPSloppy(req.RemoteAddr)
}
// Don't duplicate remote IP if it's already the last address in the chain.
if remoteIP != nil && (len(srcIPs) == 0 || !remoteIP.Equal(srcIPs[len(srcIPs)-1])) {
srcIPs = append(srcIPs, remoteIP)
}
return srcIPs
}
// Checks whether the given IP address is contained in the list of IPs.
func containsIP(ips []net.IP, ip net.IP) bool {
for _, v := range ips {
if v.Equal(ip) {
return true
}
}
return false
}
// 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 {
ips := SourceIPs(req)
if len(ips) == 0 {
return nil
}
return ips[0]
}
// Prepares the X-Forwarded-For header for another forwarding hop by appending the previous sender's
// IP address to the X-Forwarded-For chain.
func AppendForwardedForHeader(req *http.Request) {
// Copied from net/http/httputil/reverseproxy.go:
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
}
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 (or no_proxy) envvar has a CIDR in it
noProxyEnv := os.Getenv("NO_PROXY")
if noProxyEnv == "" {
noProxyEnv = os.Getenv("no_proxy")
}
noProxyRules := strings.Split(noProxyEnv, ",")
cidrs := []*net.IPNet{}
for _, noProxyRule := range noProxyRules {
_, cidr, _ := netutils.ParseCIDRSloppy(noProxyRule)
if cidr != nil {
cidrs = append(cidrs, cidr)
}
}
if len(cidrs) == 0 {
return delegate
}
return func(req *http.Request) (*url.URL, error) {
ip := netutils.ParseIPSloppy(req.URL.Hostname())
if ip == nil {
return delegate(req)
}
for _, cidr := range cidrs {
if cidr.Contains(ip) {
return nil, nil
}
}
return delegate(req)
}
}
// DialerFunc implements Dialer for the provided function.
type DialerFunc func(req *http.Request) (net.Conn, error)
func (fn DialerFunc) Dial(req *http.Request) (net.Conn, error) {
return fn(req)
}
// Dialer dials a host and writes a request to it.
type Dialer interface {
// Dial connects to the host specified by req's URL, writes the request to the connection, and
// returns the opened net.Conn.
Dial(req *http.Request) (net.Conn, error)
}
// CloneRequest creates a shallow copy of the request along with a deep copy of the Headers.
func CloneRequest(req *http.Request) *http.Request {
r := new(http.Request)
// shallow clone
*r = *req
// deep copy headers
r.Header = CloneHeader(req.Header)
return r
}
// CloneHeader creates a deep copy of an http.Header.
func CloneHeader(in http.Header) http.Header {
out := make(http.Header, len(in))
for key, values := range in {
newValues := make([]string, len(values))
copy(newValues, values)
out[key] = newValues
}
return out
}
// WarningHeader contains a single RFC2616 14.46 warnings header
type WarningHeader struct {
// Codeindicates the type of warning. 299 is a miscellaneous persistent warning
Code int
// Agent contains the name or pseudonym of the server adding the Warning header.
// A single "-" is recommended when agent is unknown.
Agent string
// Warning text
Text string
}
// ParseWarningHeaders extract RFC2616 14.46 warnings headers from the specified set of header values.
// Multiple comma-separated warnings per header are supported.
// If errors are encountered on a header, the remainder of that header are skipped and subsequent headers are parsed.
// Returns successfully parsed warnings and any errors encountered.
func ParseWarningHeaders(headers []string) ([]WarningHeader, []error) {
var (
results []WarningHeader
errs []error
)
for _, header := range headers {
for len(header) > 0 {
result, remainder, err := ParseWarningHeader(header)
if err != nil {
errs = append(errs, err)
break
}
results = append(results, result)
header = remainder
}
}
return results, errs
}
var (
codeMatcher = regexp.MustCompile(`^[0-9]{3}$`)
wordDecoder = &mime.WordDecoder{}
)
// ParseWarningHeader extracts one RFC2616 14.46 warning from the specified header,
// returning an error if the header does not contain a correctly formatted warning.
// Any remaining content in the header is returned.
func ParseWarningHeader(header string) (result WarningHeader, remainder string, err error) {
// https://tools.ietf.org/html/rfc2616#section-14.46
// updated by
// https://tools.ietf.org/html/rfc7234#section-5.5
// https://tools.ietf.org/html/rfc7234#appendix-A
// Some requirements regarding production and processing of the Warning
// header fields have been relaxed, as it is not widely implemented.
// Furthermore, the Warning header field no longer uses RFC 2047
// encoding, nor does it allow multiple languages, as these aspects were
// not implemented.
//
// Format is one of:
// warn-code warn-agent "warn-text"
// warn-code warn-agent "warn-text" "warn-date"
//
// warn-code is a three digit number
// warn-agent is unquoted and contains no spaces
// warn-text is quoted with backslash escaping (RFC2047-encoded according to RFC2616, not encoded according to RFC7234)
// warn-date is optional, quoted, and in HTTP-date format (no embedded or escaped quotes)
//
// additional warnings can optionally be included in the same header by comma-separating them:
// warn-code warn-agent "warn-text" "warn-date"[, warn-code warn-agent "warn-text" "warn-date", ...]
// tolerate leading whitespace
header = strings.TrimSpace(header)
parts := strings.SplitN(header, " ", 3)
if len(parts) != 3 {
return WarningHeader{}, "", errors.New("invalid warning header: fewer than 3 segments")
}
code, agent, textDateRemainder := parts[0], parts[1], parts[2]
// verify code format
if !codeMatcher.Match([]byte(code)) {
return WarningHeader{}, "", errors.New("invalid warning header: code segment is not 3 digits between 100-299")
}
codeInt, _ := strconv.ParseInt(code, 10, 64)
// verify agent presence
if len(agent) == 0 {
return WarningHeader{}, "", errors.New("invalid warning header: empty agent segment")
}
if !utf8.ValidString(agent) || hasAnyRunes(agent, unicode.IsControl) {
return WarningHeader{}, "", errors.New("invalid warning header: invalid agent")
}
// verify textDateRemainder presence
if len(textDateRemainder) == 0 {
return WarningHeader{}, "", errors.New("invalid warning header: empty text segment")
}
// extract text
text, dateAndRemainder, err := parseQuotedString(textDateRemainder)
if err != nil {
return WarningHeader{}, "", fmt.Errorf("invalid warning header: %v", err)
}
// tolerate RFC2047-encoded text from warnings produced according to RFC2616
if decodedText, err := wordDecoder.DecodeHeader(text); err == nil {
text = decodedText
}
if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) {
return WarningHeader{}, "", errors.New("invalid warning header: invalid text")
}
result = WarningHeader{Code: int(codeInt), Agent: agent, Text: text}
if len(dateAndRemainder) > 0 {
if dateAndRemainder[0] == '"' {
// consume date
foundEndQuote := false
for i := 1; i < len(dateAndRemainder); i++ {
if dateAndRemainder[i] == '"' {
foundEndQuote = true
remainder = strings.TrimSpace(dateAndRemainder[i+1:])
break
}
}
if !foundEndQuote {
return WarningHeader{}, "", errors.New("invalid warning header: unterminated date segment")
}
} else {
remainder = dateAndRemainder
}
}
if len(remainder) > 0 {
if remainder[0] == ',' {
// consume comma if present
remainder = strings.TrimSpace(remainder[1:])
} else {
return WarningHeader{}, "", errors.New("invalid warning header: unexpected token after warn-date")
}
}
return result, remainder, nil
}
func parseQuotedString(quotedString string) (string, string, error) {
if len(quotedString) == 0 {
return "", "", errors.New("invalid quoted string: 0-length")
}
if quotedString[0] != '"' {
return "", "", errors.New("invalid quoted string: missing initial quote")
}
quotedString = quotedString[1:]
var remainder string
escaping := false
closedQuote := false
result := &strings.Builder{}
loop:
for i := 0; i < len(quotedString); i++ {
b := quotedString[i]
switch b {
case '"':
if escaping {
result.WriteByte(b)
escaping = false
} else {
closedQuote = true
remainder = strings.TrimSpace(quotedString[i+1:])
break loop
}
case '\\':
if escaping {
result.WriteByte(b)
escaping = false
} else {
escaping = true
}
default:
result.WriteByte(b)
escaping = false
}
}
if !closedQuote {
return "", "", errors.New("invalid quoted string: missing closing quote")
}
return result.String(), remainder, nil
}
func NewWarningHeader(code int, agent, text string) (string, error) {
if code < 0 || code > 999 {
return "", errors.New("code must be between 0 and 999")
}
if len(agent) == 0 {
agent = "-"
} else if !utf8.ValidString(agent) || strings.ContainsAny(agent, `\"`) || hasAnyRunes(agent, unicode.IsSpace, unicode.IsControl) {
return "", errors.New("agent must be valid UTF-8 and must not contain spaces, quotes, backslashes, or control characters")
}
if !utf8.ValidString(text) || hasAnyRunes(text, unicode.IsControl) {
return "", errors.New("text must be valid UTF-8 and must not contain control characters")
}
return fmt.Sprintf("%03d %s %s", code, agent, makeQuotedString(text)), nil
}
func hasAnyRunes(s string, runeCheckers ...func(rune) bool) bool {
for _, r := range s {
for _, checker := range runeCheckers {
if checker(r) {
return true
}
}
}
return false
}
func makeQuotedString(s string) string {
result := &bytes.Buffer{}
// opening quote
result.WriteRune('"')
for _, c := range s {
switch c {
case '"', '\\':
// escape " and \
result.WriteRune('\\')
result.WriteRune(c)
default:
// write everything else as-is
result.WriteRune(c)
}
}
// closing quote
result.WriteRune('"')
return result.String()
}

View File

@ -0,0 +1,500 @@
/*
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 (
"bufio"
"encoding/hex"
"fmt"
"io"
"net"
"os"
"strings"
"k8s.io/klog/v2"
netutils "k8s.io/utils/net"
)
type AddressFamily uint
const (
familyIPv4 AddressFamily = 4
familyIPv6 AddressFamily = 6
)
type AddressFamilyPreference []AddressFamily
var (
preferIPv4 = AddressFamilyPreference{familyIPv4, familyIPv6}
preferIPv6 = AddressFamilyPreference{familyIPv6, familyIPv4}
)
const (
// LoopbackInterfaceName is the default name of the loopback interface
LoopbackInterfaceName = "lo"
)
const (
ipv4RouteFile = "/proc/net/route"
ipv6RouteFile = "/proc/net/ipv6_route"
)
type Route struct {
Interface string
Destination net.IP
Gateway net.IP
Family AddressFamily
}
type RouteFile struct {
name string
parse func(input io.Reader) ([]Route, error)
}
// noRoutesError can be returned in case of no routes
type noRoutesError struct {
message string
}
func (e noRoutesError) Error() string {
return e.message
}
// IsNoRoutesError checks if an error is of type noRoutesError
func IsNoRoutesError(err error) bool {
if err == nil {
return false
}
switch err.(type) {
case noRoutesError:
return true
default:
return false
}
}
var (
v4File = RouteFile{name: ipv4RouteFile, parse: getIPv4DefaultRoutes}
v6File = RouteFile{name: ipv6RouteFile, parse: getIPv6DefaultRoutes}
)
func (rf RouteFile) extract() ([]Route, error) {
file, err := os.Open(rf.name)
if err != nil {
return nil, err
}
defer file.Close()
return rf.parse(file)
}
// getIPv4DefaultRoutes obtains the IPv4 routes, and filters out non-default routes.
func getIPv4DefaultRoutes(input io.Reader) ([]Route, error) {
routes := []Route{}
scanner := bufio.NewReader(input)
for {
line, err := scanner.ReadString('\n')
if err == io.EOF {
break
}
//ignore the headers in the route info
if strings.HasPrefix(line, "Iface") {
continue
}
fields := strings.Fields(line)
// Interested in fields:
// 0 - interface name
// 1 - destination address
// 2 - gateway
dest, err := parseIP(fields[1], familyIPv4)
if err != nil {
return nil, err
}
gw, err := parseIP(fields[2], familyIPv4)
if err != nil {
return nil, err
}
if !dest.Equal(net.IPv4zero) {
continue
}
routes = append(routes, Route{
Interface: fields[0],
Destination: dest,
Gateway: gw,
Family: familyIPv4,
})
}
return routes, nil
}
func getIPv6DefaultRoutes(input io.Reader) ([]Route, error) {
routes := []Route{}
scanner := bufio.NewReader(input)
for {
line, err := scanner.ReadString('\n')
if err == io.EOF {
break
}
fields := strings.Fields(line)
// Interested in fields:
// 0 - destination address
// 4 - gateway
// 9 - interface name
dest, err := parseIP(fields[0], familyIPv6)
if err != nil {
return nil, err
}
gw, err := parseIP(fields[4], familyIPv6)
if err != nil {
return nil, err
}
if !dest.Equal(net.IPv6zero) {
continue
}
if gw.Equal(net.IPv6zero) {
continue // loopback
}
routes = append(routes, Route{
Interface: fields[9],
Destination: dest,
Gateway: gw,
Family: familyIPv6,
})
}
return routes, nil
}
// parseIP takes the hex IP address string from route file and converts it
// to a net.IP address. For IPv4, the value must be converted to big endian.
func parseIP(str string, family AddressFamily) (net.IP, error) {
if str == "" {
return nil, fmt.Errorf("input is nil")
}
bytes, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
if family == familyIPv4 {
if len(bytes) != net.IPv4len {
return nil, fmt.Errorf("invalid IPv4 address in route")
}
return net.IP([]byte{bytes[3], bytes[2], bytes[1], bytes[0]}), nil
}
// Must be IPv6
if len(bytes) != net.IPv6len {
return nil, fmt.Errorf("invalid IPv6 address in route")
}
return net.IP(bytes), nil
}
func isInterfaceUp(intf *net.Interface) bool {
if intf == nil {
return false
}
if intf.Flags&net.FlagUp != 0 {
klog.V(4).Infof("Interface %v is up", intf.Name)
return true
}
return false
}
func isLoopbackOrPointToPoint(intf *net.Interface) bool {
return intf.Flags&(net.FlagLoopback|net.FlagPointToPoint) != 0
}
// getMatchingGlobalIP returns the first valid global unicast address of the given
// 'family' from the list of 'addrs'.
func getMatchingGlobalIP(addrs []net.Addr, family AddressFamily) (net.IP, error) {
if len(addrs) > 0 {
for i := range addrs {
klog.V(4).Infof("Checking addr %s.", addrs[i].String())
ip, _, err := netutils.ParseCIDRSloppy(addrs[i].String())
if err != nil {
return nil, err
}
if memberOf(ip, family) {
if ip.IsGlobalUnicast() {
klog.V(4).Infof("IP found %v", ip)
return ip, nil
} else {
klog.V(4).Infof("Non-global unicast address found %v", ip)
}
} else {
klog.V(4).Infof("%v is not an IPv%d address", ip, int(family))
}
}
}
return nil, nil
}
// getIPFromInterface gets the IPs on an interface and returns a global unicast address, if any. The
// interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromInterface(intfName string, forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intf, err := nw.InterfaceByName(intfName)
if err != nil {
return nil, err
}
if isInterfaceUp(intf) {
addrs, err := nw.Addrs(intf)
if err != nil {
return nil, err
}
klog.V(4).Infof("Interface %q has %d addresses :%v.", intfName, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intfName)
return matchingIP, nil
}
}
return nil, nil
}
// getIPFromLoopbackInterface gets the IPs on a loopback interface and returns a global unicast address, if any.
// The loopback interface must be up, the IP must in the family requested, and the IP must be a global unicast address.
func getIPFromLoopbackInterface(forFamily AddressFamily, nw networkInterfacer) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
}
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
continue
}
if intf.Flags&(net.FlagLoopback) != 0 {
addrs, err := nw.Addrs(&intf)
if err != nil {
return nil, err
}
klog.V(4).Infof("Interface %q has %d addresses :%v.", intf.Name, len(addrs), addrs)
matchingIP, err := getMatchingGlobalIP(addrs, forFamily)
if err != nil {
return nil, err
}
if matchingIP != nil {
klog.V(4).Infof("Found valid IPv%d address %v for interface %q.", int(forFamily), matchingIP, intf.Name)
return matchingIP, nil
}
}
}
return nil, nil
}
// memberOf tells if the IP is of the desired family. Used for checking interface addresses.
func memberOf(ip net.IP, family AddressFamily) bool {
if ip.To4() != nil {
return family == familyIPv4
} else {
return family == familyIPv6
}
}
// chooseIPFromHostInterfaces looks at all system interfaces, trying to find one that is up that
// has a global unicast address (non-loopback, non-link local, non-point2point), and returns the IP.
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseIPFromHostInterfaces(nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
intfs, err := nw.Interfaces()
if err != nil {
return nil, err
}
if len(intfs) == 0 {
return nil, fmt.Errorf("no interfaces found on host.")
}
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for system interface with a global IPv%d address", uint(family))
for _, intf := range intfs {
if !isInterfaceUp(&intf) {
klog.V(4).Infof("Skipping: down interface %q", intf.Name)
continue
}
if isLoopbackOrPointToPoint(&intf) {
klog.V(4).Infof("Skipping: LB or P2P interface %q", intf.Name)
continue
}
addrs, err := nw.Addrs(&intf)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
klog.V(4).Infof("Skipping: no addresses on interface %q", intf.Name)
continue
}
for _, addr := range addrs {
ip, _, err := netutils.ParseCIDRSloppy(addr.String())
if err != nil {
return nil, fmt.Errorf("unable to parse CIDR for interface %q: %s", intf.Name, err)
}
if !memberOf(ip, family) {
klog.V(4).Infof("Skipping: no address family match for %q on interface %q.", ip, intf.Name)
continue
}
// TODO: Decide if should open up to allow IPv6 LLAs in future.
if !ip.IsGlobalUnicast() {
klog.V(4).Infof("Skipping: non-global address %q on interface %q.", ip, intf.Name)
continue
}
klog.V(4).Infof("Found global unicast address %q on interface %q.", ip, intf.Name)
return ip, nil
}
}
}
return nil, fmt.Errorf("no acceptable interface with global unicast address found on host")
}
// ChooseHostInterface is a method used fetch an IP for a daemon.
// If there is no routing info file, it will choose a global IP from the system
// interfaces. Otherwise, it will use IPv4 and IPv6 route information to return the
// IP of the interface with a gateway on it (with priority given to IPv4). For a node
// with no internet connection, it returns error.
func ChooseHostInterface() (net.IP, error) {
return chooseHostInterface(preferIPv4)
}
func chooseHostInterface(addressFamilies AddressFamilyPreference) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
if _, err := os.Stat(ipv4RouteFile); os.IsNotExist(err) {
return chooseIPFromHostInterfaces(nw, addressFamilies)
}
routes, err := getAllDefaultRoutes()
if err != nil {
return nil, err
}
return chooseHostInterfaceFromRoute(routes, nw, addressFamilies)
}
// networkInterfacer defines an interface for several net library functions. Production
// code will forward to net library functions, and unit tests will override the methods
// for testing purposes.
type networkInterfacer interface {
InterfaceByName(intfName string) (*net.Interface, error)
Addrs(intf *net.Interface) ([]net.Addr, error)
Interfaces() ([]net.Interface, error)
}
// networkInterface implements the networkInterfacer interface for production code, just
// wrapping the underlying net library function calls.
type networkInterface struct{}
func (_ networkInterface) InterfaceByName(intfName string) (*net.Interface, error) {
return net.InterfaceByName(intfName)
}
func (_ networkInterface) Addrs(intf *net.Interface) ([]net.Addr, error) {
return intf.Addrs()
}
func (_ networkInterface) Interfaces() ([]net.Interface, error) {
return net.Interfaces()
}
// getAllDefaultRoutes obtains IPv4 and IPv6 default routes on the node. If unable
// to read the IPv4 routing info file, we return an error. If unable to read the IPv6
// routing info file (which is optional), we'll just use the IPv4 route information.
// Using all the routing info, if no default routes are found, an error is returned.
func getAllDefaultRoutes() ([]Route, error) {
routes, err := v4File.extract()
if err != nil {
return nil, err
}
v6Routes, _ := v6File.extract()
routes = append(routes, v6Routes...)
if len(routes) == 0 {
return nil, noRoutesError{
message: fmt.Sprintf("no default routes found in %q or %q", v4File.name, v6File.name),
}
}
return routes, nil
}
// chooseHostInterfaceFromRoute cycles through each default route provided, looking for a
// global IP address from the interface for the route. If there are routes but no global
// address is obtained from the interfaces, it checks if the loopback interface has a global address.
// addressFamilies determines whether it prefers IPv4 or IPv6
func chooseHostInterfaceFromRoute(routes []Route, nw networkInterfacer, addressFamilies AddressFamilyPreference) (net.IP, error) {
for _, family := range addressFamilies {
klog.V(4).Infof("Looking for default routes with IPv%d addresses", uint(family))
for _, route := range routes {
if route.Family != family {
continue
}
klog.V(4).Infof("Default route transits interface %q", route.Interface)
finalIP, err := getIPFromInterface(route.Interface, family, nw)
if err != nil {
return nil, err
}
if finalIP != nil {
klog.V(4).Infof("Found active IP %v ", finalIP)
return finalIP, nil
}
// In case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// the global IP is assigned to the loopback interface, and we should use it
loopbackIP, err := getIPFromLoopbackInterface(family, nw)
if err != nil {
return nil, err
}
if loopbackIP != nil {
klog.V(4).Infof("Found active IP %v on Loopback interface", loopbackIP)
return loopbackIP, nil
}
}
}
klog.V(4).Infof("No active IP found by looking at default routes")
return nil, fmt.Errorf("unable to select an IP from default routes.")
}
// ResolveBindAddress returns the IP address of a daemon, based on the given bindAddress:
// If bindAddress is unset, it returns the host's default IP, as with ChooseHostInterface().
// If bindAddress is unspecified or loopback, it returns the default IP of the same
// address family as bindAddress.
// Otherwise, it just returns bindAddress.
func ResolveBindAddress(bindAddress net.IP) (net.IP, error) {
addressFamilies := preferIPv4
if bindAddress != nil && memberOf(bindAddress, familyIPv6) {
addressFamilies = preferIPv6
}
if bindAddress == nil || bindAddress.IsUnspecified() || bindAddress.IsLoopback() {
hostIP, err := chooseHostInterface(addressFamilies)
if err != nil {
return nil, err
}
bindAddress = hostIP
}
return bindAddress, nil
}
// ChooseBindAddressForInterface choose a global IP for a specific interface, with priority given to IPv4.
// This is required in case of network setups where default routes are present, but network
// interfaces use only link-local addresses (e.g. as described in RFC5549).
// e.g when using BGP to announce a host IP over link-local ip addresses and this ip address is attached to the lo interface.
func ChooseBindAddressForInterface(intfName string) (net.IP, error) {
var nw networkInterfacer = networkInterface{}
for _, family := range preferIPv4 {
ip, err := getIPFromInterface(intfName, family, nw)
if err != nil {
return nil, err
}
if ip != nil {
return ip, nil
}
}
return nil, fmt.Errorf("unable to select an IP from %s network interface", intfName)
}

View File

@ -0,0 +1,149 @@
/*
Copyright 2015 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 (
"fmt"
"strconv"
"strings"
)
// PortRange represents a range of TCP/UDP ports. To represent a single port,
// set Size to 1.
type PortRange struct {
Base int
Size int
}
// Contains tests whether a given port falls within the PortRange.
func (pr *PortRange) Contains(p int) bool {
return (p >= pr.Base) && ((p - pr.Base) < pr.Size)
}
// String converts the PortRange to a string representation, which can be
// parsed by PortRange.Set or ParsePortRange.
func (pr PortRange) String() string {
if pr.Size == 0 {
return ""
}
return fmt.Sprintf("%d-%d", pr.Base, pr.Base+pr.Size-1)
}
// Set parses a string of the form "value", "min-max", or "min+offset", inclusive at both ends, and
// sets the PortRange from it. This is part of the flag.Value and pflag.Value
// interfaces.
func (pr *PortRange) Set(value string) error {
const (
SinglePortNotation = 1 << iota
HyphenNotation
PlusNotation
)
value = strings.TrimSpace(value)
hyphenIndex := strings.Index(value, "-")
plusIndex := strings.Index(value, "+")
if value == "" {
pr.Base = 0
pr.Size = 0
return nil
}
var err error
var low, high int
var notation int
if plusIndex == -1 && hyphenIndex == -1 {
notation |= SinglePortNotation
}
if hyphenIndex != -1 {
notation |= HyphenNotation
}
if plusIndex != -1 {
notation |= PlusNotation
}
switch notation {
case SinglePortNotation:
var port int
port, err = strconv.Atoi(value)
if err != nil {
return err
}
low = port
high = port
case HyphenNotation:
low, err = strconv.Atoi(value[:hyphenIndex])
if err != nil {
return err
}
high, err = strconv.Atoi(value[hyphenIndex+1:])
if err != nil {
return err
}
case PlusNotation:
var offset int
low, err = strconv.Atoi(value[:plusIndex])
if err != nil {
return err
}
offset, err = strconv.Atoi(value[plusIndex+1:])
if err != nil {
return err
}
high = low + offset
default:
return fmt.Errorf("unable to parse port range: %s", value)
}
if low > 65535 || high > 65535 {
return fmt.Errorf("the port range cannot be greater than 65535: %s", value)
}
if high < low {
return fmt.Errorf("end port cannot be less than start port: %s", value)
}
pr.Base = low
pr.Size = 1 + high - low
return nil
}
// Type returns a descriptive string about this type. This is part of the
// pflag.Value interface.
func (*PortRange) Type() string {
return "portRange"
}
// ParsePortRange parses a string of the form "min-max", inclusive at both
// ends, and initializes a new PortRange from it.
func ParsePortRange(value string) (*PortRange, error) {
pr := &PortRange{}
err := pr.Set(value)
if err != nil {
return nil, err
}
return pr, nil
}
func ParsePortRangeOrDie(value string) *PortRange {
pr, err := ParsePortRange(value)
if err != nil {
panic(fmt.Sprintf("couldn't parse port range %q: %v", value, err))
}
return pr
}

View File

@ -0,0 +1,78 @@
/*
Copyright 2015 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 (
"strings"
"k8s.io/apimachinery/pkg/util/sets"
)
var validSchemes = sets.NewString("http", "https", "")
// SplitSchemeNamePort takes a string of the following forms:
// - "<name>", returns "", "<name>","", true
// - "<name>:<port>", returns "", "<name>","<port>",true
// - "<scheme>:<name>:<port>", returns "<scheme>","<name>","<port>",true
//
// Name must be non-empty or valid will be returned false.
// Scheme must be "http" or "https" if specified
// Port is returned as a string, and it is not required to be numeric (could be
// used for a named port, for example).
func SplitSchemeNamePort(id string) (scheme, name, port string, valid bool) {
parts := strings.Split(id, ":")
switch len(parts) {
case 1:
name = parts[0]
case 2:
name = parts[0]
port = parts[1]
case 3:
scheme = parts[0]
name = parts[1]
port = parts[2]
default:
return "", "", "", false
}
if len(name) > 0 && validSchemes.Has(scheme) {
return scheme, name, port, true
} else {
return "", "", "", false
}
}
// JoinSchemeNamePort returns a string that specifies the scheme, name, and port:
// - "<name>"
// - "<name>:<port>"
// - "<scheme>:<name>:<port>"
//
// None of the parameters may contain a ':' character
// Name is required
// Scheme must be "", "http", or "https"
func JoinSchemeNamePort(scheme, name, port string) string {
if len(scheme) > 0 {
// Must include three segments to specify scheme
return scheme + ":" + name + ":" + port
}
if len(port) > 0 {
// Must include two segments to specify port
return name + ":" + port
}
// Return name alone
return name
}

63
api/vendor/k8s.io/apimachinery/pkg/util/net/util.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
/*
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 (
"errors"
"net"
"reflect"
"strings"
"syscall"
)
// IPNetEqual checks if the two input IPNets are representing the same subnet.
// For example,
//
// 10.0.0.1/24 and 10.0.0.0/24 are the same subnet.
// 10.0.0.1/24 and 10.0.0.0/25 are not the same subnet.
func IPNetEqual(ipnet1, ipnet2 *net.IPNet) bool {
if ipnet1 == nil || ipnet2 == nil {
return false
}
if reflect.DeepEqual(ipnet1.Mask, ipnet2.Mask) && ipnet1.Contains(ipnet2.IP) && ipnet2.Contains(ipnet1.IP) {
return true
}
return false
}
// Returns if the given err is "connection reset by peer" error.
func IsConnectionReset(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == syscall.ECONNRESET
}
return false
}
// Returns if the given err is "http2: client connection lost" error.
func IsHTTP2ConnectionLost(err error) bool {
return err != nil && strings.Contains(err.Error(), "http2: client connection lost")
}
// Returns if the given err is "connection refused" error
func IsConnectionRefused(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
return errno == syscall.ECONNREFUSED
}
return false
}