1 package emissaryutil
2
3 import (
4 "fmt"
5 "net"
6 "net/url"
7 "strconv"
8 "strings"
9 )
10
11
12 const schemeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-."
13
14
15
16 func wouldConfuseURLParse(url string) bool {
17 if strings.Contains(url, ":") && strings.HasPrefix(strings.TrimLeft(url, schemeChars), "://") {
18
19 return false
20 }
21 if strings.HasPrefix(url, "//") {
22
23 return false
24 }
25 return true
26 }
27
28 type GlobalResolverConfig interface {
29 AmbassadorNamespace() string
30 UseAmbassadorNamespaceForServiceResolution() bool
31 }
32
33
34
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
49
50
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
70
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
78
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
102
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