github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/client/internal/endpoint/endpoint.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // Copyright 2021 The etcd Authors 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // This file is copied from Etcd's source code. The original's link: 17 // https://github.com/etcd-io/etcd/blob/ae36a577d7becbdeebf1f0fb665573b721e435f8/client/v3/internal/endpoint/endpoint.go 18 19 package endpoint 20 21 import ( 22 "fmt" 23 "net" 24 "net/url" 25 "path" 26 "strings" 27 ) 28 29 // CredsRequirement indicates whether a certificate is needed. 30 type CredsRequirement int 31 32 const ( 33 // CredsRequire - Credentials/certificate required for thi type of connection. 34 CredsRequire CredsRequirement = iota 35 // CredsDrop - Credentials/certificate not needed and should get ignored. 36 CredsDrop 37 // CredsOptional - Credentials/certificate might be used if supplied 38 CredsOptional 39 ) 40 41 func extractHostFromHostPort(ep string) string { 42 host, _, err := net.SplitHostPort(ep) 43 if err != nil { 44 return ep 45 } 46 return host 47 } 48 49 func extractHostFromPath(pathStr string) string { 50 return extractHostFromHostPort(path.Base(pathStr)) 51 } 52 53 // mustSplit2 returns the values from strings.SplitN(s, sep, 2). 54 // If sep is not found, it returns ("", "") instead. 55 func mustSplit2(s, sep string) (string, string) { 56 spl := strings.SplitN(s, sep, 2) 57 if len(spl) < 2 { 58 panic(fmt.Errorf("token '%v' expected to have separator sep: `%v`", s, sep)) 59 } 60 return spl[0], spl[1] 61 } 62 63 func schemeToCredsRequirement(schema string) CredsRequirement { 64 switch schema { 65 case "https", "unixs": 66 return CredsRequire 67 case "http": 68 return CredsDrop 69 case "unix": 70 // Preserving previous behavior from: 71 // https://github.com/etcd-io/etcd/blob/dae29bb719dd69dc119146fc297a0628fcc1ccf8/client/v3/client.go#L212 72 // that likely was a bug due to missing 'fallthrough'. 73 // At the same time it seems legit to let the users decide whether they 74 // want credential control or not (and 'unixs' schema is not a standard thing). 75 return CredsOptional 76 case "": 77 return CredsOptional 78 default: 79 return CredsOptional 80 } 81 } 82 83 // This function translates endpoints names supported by etcd server into 84 // endpoints as supported by grpc with additional information 85 // (server_name for cert validation, requireCreds - whether certs are needed). 86 // The main differences: 87 // - etcd supports unixs & https names as opposed to unix & http to 88 // distinguish need to configure certificates. 89 // - etcd support http(s) names as opposed to tcp supported by grpc/dial method. 90 // - etcd supports unix(s)://local-file naming schema 91 // (as opposed to unix:local-file canonical name used by grpc for current dir files). 92 // - Within the unix(s) schemas, the last segment (filename) without 'port' (content after colon) 93 // is considered serverName - to allow local testing of cert-protected communication. 94 // 95 // See more: 96 // - https://github.com/grpc/grpc-go/blob/26c143bd5f59344a4b8a1e491e0f5e18aa97abc7/internal/grpcutil/target.go#L47 97 // - https://golang.org/pkg/net/#Dial 98 // - https://github.com/grpc/grpc/blob/master/doc/naming.md 99 func translateEndpoint(ep string) (addr string, serverName string, requireCreds CredsRequirement) { 100 if strings.HasPrefix(ep, "unix:") || strings.HasPrefix(ep, "unixs:") { 101 if strings.HasPrefix(ep, "unix:///") || strings.HasPrefix(ep, "unixs:///") { 102 // absolute path case 103 schema, absolutePath := mustSplit2(ep, "://") 104 return "unix://" + absolutePath, extractHostFromPath(absolutePath), schemeToCredsRequirement(schema) 105 } 106 if strings.HasPrefix(ep, "unix://") || strings.HasPrefix(ep, "unixs://") { 107 // legacy etcd local path 108 schema, localPath := mustSplit2(ep, "://") 109 return "unix:" + localPath, extractHostFromPath(localPath), schemeToCredsRequirement(schema) 110 } 111 schema, localPath := mustSplit2(ep, ":") 112 return "unix:" + localPath, extractHostFromPath(localPath), schemeToCredsRequirement(schema) 113 } 114 115 if strings.Contains(ep, "://") { 116 url, err := url.Parse(ep) 117 if err != nil { 118 return ep, extractHostFromHostPort(ep), CredsOptional 119 } 120 if url.Scheme == "http" || url.Scheme == "https" { 121 return url.Host, url.Hostname(), schemeToCredsRequirement(url.Scheme) 122 } 123 return ep, url.Hostname(), schemeToCredsRequirement(url.Scheme) 124 } 125 // Handles plain addresses like 10.0.0.44:437. 126 return ep, extractHostFromHostPort(ep), CredsOptional 127 } 128 129 // RequiresCredentials returns whether given endpoint requires 130 // credentials/certificates for connection. 131 func RequiresCredentials(ep string) CredsRequirement { 132 _, _, requireCreds := translateEndpoint(ep) 133 return requireCreds 134 } 135 136 // Interpret endpoint parses an endpoint of the form 137 // (http|https)://<host>*|(unix|unixs)://<path>) 138 // and returns low-level address (supported by 'net') to connect to, 139 // and a server name used for x509 certificate matching. 140 func Interpret(ep string) (address string, serverName string) { 141 addr, serverName, _ := translateEndpoint(ep) 142 return addr, serverName 143 }