...

Source file src/github.com/emissary-ingress/emissary/v3/pkg/emissaryutil/svc.go

Documentation: github.com/emissary-ingress/emissary/v3/pkg/emissaryutil

     1  package emissaryutil
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/url"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  // schemeChars mimics Python `from urllib.parse import scheme_chars`.
    12  const schemeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-."
    13  
    14  // wouldConfuseURLParse mimics `python/ambassador/ir/irbasemapping.py:would_confuse_urlparse()`.
    15  // Please keep them in-sync.
    16  func wouldConfuseURLParse(url string) bool {
    17  	if strings.Contains(url, ":") && strings.HasPrefix(strings.TrimLeft(url, schemeChars), "://") {
    18  		// has a scheme
    19  		return false
    20  	}
    21  	if strings.HasPrefix(url, "//") {
    22  		// does not have a scheme, but has the "//" URL authority marker
    23  		return false
    24  	}
    25  	return true
    26  }
    27  
    28  type GlobalResolverConfig interface {
    29  	AmbassadorNamespace() string
    30  	UseAmbassadorNamespaceForServiceResolution() bool
    31  }
    32  
    33  // ParseServiceName mimics the first half of
    34  // `python/ambassador/ir/irbasemapping.py:normalize_service_name()`.  Please keep them in-sync.
    35  func ParseServiceName(svcStr string) (scheme, hostname string, port uint16, err error) {
    36  	origSvcStr := svcStr
    37  	if wouldConfuseURLParse(svcStr) {
    38  		svcStr = "//" + svcStr
    39  	}
    40  	parsed, err := url.Parse(svcStr)
    41  	if err != nil {
    42  		return "", "", 0, fmt.Errorf("service %q: %w", origSvcStr, err)
    43  	}
    44  	scheme = parsed.Scheme
    45  	hostname = parsed.Hostname()
    46  	portStr := parsed.Port()
    47  	if portStr != "" {
    48  		// Use net.SplitHostPort because does validation that we want; compared to
    49  		// net/url.URL.{Hostname,Port}(), which do the same splitting but not the
    50  		// validation.
    51  		hostname, portStr, err = net.SplitHostPort(parsed.Host)
    52  		if err != nil {
    53  			return "", "", 0, fmt.Errorf("service %q: %w", origSvcStr, err)
    54  		}
    55  	}
    56  	if hostname == "" {
    57  		return "", "", 0, fmt.Errorf("service %q: address %s: no hostname", origSvcStr, parsed.Host)
    58  	}
    59  	var port64 uint64
    60  	if portStr != "" {
    61  		port64, err = strconv.ParseUint(portStr, 10, 16)
    62  		if err != nil {
    63  			return "", "", 0, fmt.Errorf("service %q: port %s: %w", origSvcStr, portStr, err)
    64  		}
    65  	}
    66  	return scheme, hostname, uint16(port64), nil
    67  }
    68  
    69  // NormalizeServiceName mimics `python/ambassador/ir/irbasemapping.py:normalize_service_name()`.
    70  // Please keep them in-sync.
    71  func NormalizeServiceName(ir GlobalResolverConfig, svcStr, mappingNamespace, resolverKind string) (string, error) {
    72  	scheme, hostname, port, err := ParseServiceName(svcStr)
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  
    77  	// Consul Resolvers don't allow service names to include subdomains, but
    78  	// Kubernetes Resolvers _require_ subdomains to correctly handle namespaces.
    79  	wantQualified := !ir.UseAmbassadorNamespaceForServiceResolution() && strings.HasPrefix(resolverKind, "Kubernetes")
    80  
    81  	isQualified := strings.ContainsAny(hostname, ".:") || hostname == "localhost"
    82  
    83  	if mappingNamespace != "" && mappingNamespace != ir.AmbassadorNamespace() && wantQualified && !isQualified {
    84  		hostname += "." + mappingNamespace
    85  	}
    86  
    87  	ret := url.PathEscape(hostname)
    88  	if strings.Contains(ret, ":") {
    89  		ret = "[" + ret + "]"
    90  	}
    91  	if scheme != "" {
    92  		ret = scheme + "://" + ret
    93  	}
    94  	if port != 0 {
    95  		ret = fmt.Sprintf("%s:%d", ret, port)
    96  	}
    97  	return ret, nil
    98  }
    99  
   100  func netipParseAddr(s string) (net.IP, error) {
   101  	// TODO(lukeshu): Once we upgrade to Go 1.18, delete this function in favor of
   102  	// net/netip.ParseAddr().  We use this instead of net.ParseIP in order to handle IPv6 zones.
   103  	ip := net.ParseIP(strings.SplitN(s, "%", 2)[0])
   104  	if ip == nil {
   105  		return nil, fmt.Errorf("not an IP: %q", s)
   106  	}
   107  	return ip, nil
   108  }
   109  
   110  func IsLocalhost(hostname string) bool {
   111  	if hostname == "localhost" {
   112  		return true
   113  	}
   114  	ip, err := netipParseAddr(hostname)
   115  	if err != nil {
   116  		return false
   117  	}
   118  	return ip.IsLoopback()
   119  }
   120  

View as plain text