grpc: better RFC 3986 compliant target parsing (#4817)
This commit is contained in:

committed by
GitHub

parent
45097a8aa6
commit
aaff9e7ab9
184
clientconn.go
184
clientconn.go
@ -23,6 +23,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -37,7 +38,6 @@ import (
|
|||||||
"google.golang.org/grpc/internal/backoff"
|
"google.golang.org/grpc/internal/backoff"
|
||||||
"google.golang.org/grpc/internal/channelz"
|
"google.golang.org/grpc/internal/channelz"
|
||||||
"google.golang.org/grpc/internal/grpcsync"
|
"google.golang.org/grpc/internal/grpcsync"
|
||||||
"google.golang.org/grpc/internal/grpcutil"
|
|
||||||
iresolver "google.golang.org/grpc/internal/resolver"
|
iresolver "google.golang.org/grpc/internal/resolver"
|
||||||
"google.golang.org/grpc/internal/transport"
|
"google.golang.org/grpc/internal/transport"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
@ -248,38 +248,15 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the resolver to use.
|
// Determine the resolver to use.
|
||||||
cc.parsedTarget = grpcutil.ParseTarget(cc.target, cc.dopts.copts.Dialer != nil)
|
resolverBuilder, err := cc.parseTargetAndFindResolver()
|
||||||
channelz.Infof(logger, cc.channelzID, "parsed scheme: %q", cc.parsedTarget.Scheme)
|
if err != nil {
|
||||||
resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)
|
return nil, err
|
||||||
if resolverBuilder == nil {
|
|
||||||
// If resolver builder is still nil, the parsed target's scheme is
|
|
||||||
// not registered. Fallback to default resolver and set Endpoint to
|
|
||||||
// the original target.
|
|
||||||
channelz.Infof(logger, cc.channelzID, "scheme %q not registered, fallback to default scheme", cc.parsedTarget.Scheme)
|
|
||||||
cc.parsedTarget = resolver.Target{
|
|
||||||
Scheme: resolver.GetDefaultScheme(),
|
|
||||||
Endpoint: target,
|
|
||||||
}
|
|
||||||
resolverBuilder = cc.getResolver(cc.parsedTarget.Scheme)
|
|
||||||
if resolverBuilder == nil {
|
|
||||||
return nil, fmt.Errorf("could not get resolver for default scheme: %q", cc.parsedTarget.Scheme)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cc.authority, err = determineAuthority(cc.parsedTarget.Endpoint, cc.target, cc.dopts)
|
||||||
creds := cc.dopts.copts.TransportCredentials
|
if err != nil {
|
||||||
if creds != nil && creds.Info().ServerName != "" {
|
return nil, err
|
||||||
cc.authority = creds.Info().ServerName
|
|
||||||
} else if cc.dopts.insecure && cc.dopts.authority != "" {
|
|
||||||
cc.authority = cc.dopts.authority
|
|
||||||
} else if strings.HasPrefix(cc.target, "unix:") || strings.HasPrefix(cc.target, "unix-abstract:") {
|
|
||||||
cc.authority = "localhost"
|
|
||||||
} else if strings.HasPrefix(cc.parsedTarget.Endpoint, ":") {
|
|
||||||
cc.authority = "localhost" + cc.parsedTarget.Endpoint
|
|
||||||
} else {
|
|
||||||
// Use endpoint from "scheme://authority/endpoint" as the default
|
|
||||||
// authority for ClientConn.
|
|
||||||
cc.authority = cc.parsedTarget.Endpoint
|
|
||||||
}
|
}
|
||||||
|
channelz.Infof(logger, cc.channelzID, "Channel authority set to %q", cc.authority)
|
||||||
|
|
||||||
if cc.dopts.scChan != nil && !scSet {
|
if cc.dopts.scChan != nil && !scSet {
|
||||||
// Blocking wait for the initial service config.
|
// Blocking wait for the initial service config.
|
||||||
@ -902,10 +879,7 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
|
|||||||
// ac.state is Ready, try to find the connected address.
|
// ac.state is Ready, try to find the connected address.
|
||||||
var curAddrFound bool
|
var curAddrFound bool
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
// a.ServerName takes precedent over ClientConn authority, if present.
|
a.ServerName = ac.cc.getServerName(a)
|
||||||
if a.ServerName == "" {
|
|
||||||
a.ServerName = ac.cc.authority
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(ac.curAddr, a) {
|
if reflect.DeepEqual(ac.curAddr, a) {
|
||||||
curAddrFound = true
|
curAddrFound = true
|
||||||
break
|
break
|
||||||
@ -919,6 +893,26 @@ func (ac *addrConn) tryUpdateAddrs(addrs []resolver.Address) bool {
|
|||||||
return curAddrFound
|
return curAddrFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getServerName determines the serverName to be used in the connection
|
||||||
|
// handshake. The default value for the serverName is the authority on the
|
||||||
|
// ClientConn, which either comes from the user's dial target or through an
|
||||||
|
// authority override specified using the WithAuthority dial option. Name
|
||||||
|
// resolvers can specify a per-address override for the serverName through the
|
||||||
|
// resolver.Address.ServerName field which is used only if the WithAuthority
|
||||||
|
// dial option was not used. The rationale is that per-address authority
|
||||||
|
// overrides specified by the name resolver can represent a security risk, while
|
||||||
|
// an override specified by the user is more dependable since they probably know
|
||||||
|
// what they are doing.
|
||||||
|
func (cc *ClientConn) getServerName(addr resolver.Address) string {
|
||||||
|
if cc.dopts.authority != "" {
|
||||||
|
return cc.dopts.authority
|
||||||
|
}
|
||||||
|
if addr.ServerName != "" {
|
||||||
|
return addr.ServerName
|
||||||
|
}
|
||||||
|
return cc.authority
|
||||||
|
}
|
||||||
|
|
||||||
func getMethodConfig(sc *ServiceConfig, method string) MethodConfig {
|
func getMethodConfig(sc *ServiceConfig, method string) MethodConfig {
|
||||||
if sc == nil {
|
if sc == nil {
|
||||||
return MethodConfig{}
|
return MethodConfig{}
|
||||||
@ -1275,11 +1269,7 @@ func (ac *addrConn) createTransport(addr resolver.Address, copts transport.Conne
|
|||||||
prefaceReceived := grpcsync.NewEvent()
|
prefaceReceived := grpcsync.NewEvent()
|
||||||
connClosed := grpcsync.NewEvent()
|
connClosed := grpcsync.NewEvent()
|
||||||
|
|
||||||
// addr.ServerName takes precedent over ClientConn authority, if present.
|
addr.ServerName = ac.cc.getServerName(addr)
|
||||||
if addr.ServerName == "" {
|
|
||||||
addr.ServerName = ac.cc.authority
|
|
||||||
}
|
|
||||||
|
|
||||||
hctx, hcancel := context.WithCancel(ac.ctx)
|
hctx, hcancel := context.WithCancel(ac.ctx)
|
||||||
hcStarted := false // protected by ac.mu
|
hcStarted := false // protected by ac.mu
|
||||||
|
|
||||||
@ -1621,3 +1611,117 @@ func (cc *ClientConn) connectionError() error {
|
|||||||
defer cc.lceMu.Unlock()
|
defer cc.lceMu.Unlock()
|
||||||
return cc.lastConnectionError
|
return cc.lastConnectionError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cc *ClientConn) parseTargetAndFindResolver() (resolver.Builder, error) {
|
||||||
|
channelz.Infof(logger, cc.channelzID, "original dial target is: %q", cc.target)
|
||||||
|
|
||||||
|
var rb resolver.Builder
|
||||||
|
parsedTarget, err := parseTarget(cc.target)
|
||||||
|
if err != nil {
|
||||||
|
channelz.Infof(logger, cc.channelzID, "dial target %q parse failed: %v", cc.target, err)
|
||||||
|
} else {
|
||||||
|
channelz.Infof(logger, cc.channelzID, "parsed dial target is: %+v", parsedTarget)
|
||||||
|
rb = cc.getResolver(parsedTarget.Scheme)
|
||||||
|
if rb != nil {
|
||||||
|
cc.parsedTarget = parsedTarget
|
||||||
|
return rb, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are here because the user's dial target did not contain a scheme or
|
||||||
|
// specified an unregistered scheme. We should fallback to the default
|
||||||
|
// scheme, except when a custom dialer is specified in which case, we should
|
||||||
|
// always use passthrough scheme.
|
||||||
|
defScheme := resolver.GetDefaultScheme()
|
||||||
|
if cc.dopts.copts.Dialer != nil {
|
||||||
|
defScheme = "passthrough"
|
||||||
|
}
|
||||||
|
channelz.Infof(logger, cc.channelzID, "fallback to scheme %q", defScheme)
|
||||||
|
canonicalTarget := defScheme + ":///" + cc.target
|
||||||
|
|
||||||
|
parsedTarget, err = parseTarget(canonicalTarget)
|
||||||
|
if err != nil {
|
||||||
|
channelz.Infof(logger, cc.channelzID, "dial target %q parse failed: %v", canonicalTarget, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
channelz.Infof(logger, cc.channelzID, "parsed dial target is: %+v", parsedTarget)
|
||||||
|
rb = cc.getResolver(parsedTarget.Scheme)
|
||||||
|
if rb == nil {
|
||||||
|
return nil, fmt.Errorf("could not get resolver for default scheme: %q", parsedTarget.Scheme)
|
||||||
|
}
|
||||||
|
cc.parsedTarget = parsedTarget
|
||||||
|
return rb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTarget uses RFC 3986 semantics to parse the given target into a
|
||||||
|
// resolver.Target struct containing scheme, authority and endpoint. Query
|
||||||
|
// params are stripped from the endpoint.
|
||||||
|
func parseTarget(target string) (resolver.Target, error) {
|
||||||
|
u, err := url.Parse(target)
|
||||||
|
if err != nil {
|
||||||
|
return resolver.Target{}, err
|
||||||
|
}
|
||||||
|
// For targets of the form "[scheme]://[authority]/endpoint, the endpoint
|
||||||
|
// value returned from url.Parse() contains a leading "/". Although this is
|
||||||
|
// in accordance with RFC 3986, we do not want to break existing resolver
|
||||||
|
// implementations which expect the endpoint without the leading "/". So, we
|
||||||
|
// end up stripping the leading "/" here. But this will result in an
|
||||||
|
// incorrect parsing for something like "unix:///path/to/socket". Since we
|
||||||
|
// own the "unix" resolver, we can workaround in the unix resolver by using
|
||||||
|
// the `URL` field instead of the `Endpoint` field.
|
||||||
|
endpoint := u.Path
|
||||||
|
if endpoint == "" {
|
||||||
|
endpoint = u.Opaque
|
||||||
|
}
|
||||||
|
endpoint = strings.TrimPrefix(endpoint, "/")
|
||||||
|
return resolver.Target{
|
||||||
|
Scheme: u.Scheme,
|
||||||
|
Authority: u.Host,
|
||||||
|
Endpoint: endpoint,
|
||||||
|
URL: *u,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine channel authority. The order of precedence is as follows:
|
||||||
|
// - user specified authority override using `WithAuthority` dial option
|
||||||
|
// - creds' notion of server name for the authentication handshake
|
||||||
|
// - endpoint from dial target of the form "scheme://[authority]/endpoint"
|
||||||
|
func determineAuthority(endpoint, target string, dopts dialOptions) (string, error) {
|
||||||
|
// Historically, we had two options for users to specify the serverName or
|
||||||
|
// authority for a channel. One was through the transport credentials
|
||||||
|
// (either in its constructor, or through the OverrideServerName() method).
|
||||||
|
// The other option (for cases where WithInsecure() dial option was used)
|
||||||
|
// was to use the WithAuthority() dial option.
|
||||||
|
//
|
||||||
|
// A few things have changed since:
|
||||||
|
// - `insecure` package with an implementation of the `TransportCredentials`
|
||||||
|
// interface for the insecure case
|
||||||
|
// - WithAuthority() dial option support for secure credentials
|
||||||
|
authorityFromCreds := ""
|
||||||
|
if creds := dopts.copts.TransportCredentials; creds != nil && creds.Info().ServerName != "" {
|
||||||
|
authorityFromCreds = creds.Info().ServerName
|
||||||
|
}
|
||||||
|
authorityFromDialOption := dopts.authority
|
||||||
|
if (authorityFromCreds != "" && authorityFromDialOption != "") && authorityFromCreds != authorityFromDialOption {
|
||||||
|
return "", fmt.Errorf("ClientConn's authority from transport creds %q and dial option %q don't match", authorityFromCreds, authorityFromDialOption)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case authorityFromDialOption != "":
|
||||||
|
return authorityFromDialOption, nil
|
||||||
|
case authorityFromCreds != "":
|
||||||
|
return authorityFromCreds, nil
|
||||||
|
case strings.HasPrefix(target, "unix:") || strings.HasPrefix(target, "unix-abstract:"):
|
||||||
|
// TODO: remove when the unix resolver implements optional interface to
|
||||||
|
// return channel authority.
|
||||||
|
return "localhost", nil
|
||||||
|
case strings.HasPrefix(endpoint, ":"):
|
||||||
|
return "localhost" + endpoint, nil
|
||||||
|
default:
|
||||||
|
// TODO: Define an optional interface on the resolver builder to return
|
||||||
|
// the channel authority given the user's dial target. For resolvers
|
||||||
|
// which don't implement this interface, we will use the endpoint from
|
||||||
|
// "scheme://authority/endpoint" as the default authority.
|
||||||
|
return endpoint, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,10 +59,9 @@ func (s) TestClientConnAuthority(t *testing.T) {
|
|||||||
wantAuthority: "authority-override",
|
wantAuthority: "authority-override",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "override-via-creds-and-WithAuthority",
|
name: "override-via-creds-and-WithAuthority",
|
||||||
target: "Non-Existent.Server:8080",
|
target: "Non-Existent.Server:8080",
|
||||||
// WithAuthority override works only for insecure creds.
|
opts: []DialOption{WithTransportCredentials(creds), WithAuthority(serverNameOverride)},
|
||||||
opts: []DialOption{WithTransportCredentials(creds), WithAuthority("authority-override")},
|
|
||||||
wantAuthority: serverNameOverride,
|
wantAuthority: serverNameOverride,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -120,3 +119,16 @@ func (s) TestClientConnAuthority(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s) TestClientConnAuthority_CredsAndDialOptionMismatch(t *testing.T) {
|
||||||
|
serverNameOverride := "over.write.server.name"
|
||||||
|
creds, err := credentials.NewClientTLSFromFile(testdata.Path("x509/server_ca_cert.pem"), serverNameOverride)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("credentials.NewClientTLSFromFile(_, %q) failed: %v", err, serverNameOverride)
|
||||||
|
}
|
||||||
|
opts := []DialOption{WithTransportCredentials(creds), WithAuthority("authority-override")}
|
||||||
|
if cc, err := Dial("Non-Existent.Server:8000", opts...); err == nil {
|
||||||
|
cc.Close()
|
||||||
|
t.Fatal("grpc.Dial() succeeded when expected to fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,9 +22,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,45 +38,47 @@ func (s) TestParsedTarget_Success_WithoutCustomDialer(t *testing.T) {
|
|||||||
wantParsed resolver.Target
|
wantParsed resolver.Target
|
||||||
}{
|
}{
|
||||||
// No scheme is specified.
|
// No scheme is specified.
|
||||||
{target: "", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ""}},
|
{target: "", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "", URL: url.URL{Scheme: defScheme, Path: "/"}}},
|
||||||
{target: "://", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://"}},
|
{target: "://", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://", URL: url.URL{Scheme: defScheme, Path: "/://"}}},
|
||||||
{target: ":///", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///"}},
|
{target: ":///", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///", URL: url.URL{Scheme: defScheme, Path: "/:///"}}},
|
||||||
{target: "://a/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://a/"}},
|
{target: "://a/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://a/", URL: url.URL{Scheme: defScheme, Path: "/://a/"}}},
|
||||||
{target: ":///a", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///a"}},
|
{target: ":///a", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///a", URL: url.URL{Scheme: defScheme, Path: "/:///a"}}},
|
||||||
{target: "://a/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://a/b"}},
|
{target: "://a/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://a/b", URL: url.URL{Scheme: defScheme, Path: "/://a/b"}}},
|
||||||
{target: "/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "/"}},
|
{target: "/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "/", URL: url.URL{Scheme: defScheme, Path: "//"}}},
|
||||||
{target: "a/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a/b"}},
|
{target: "a/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a/b", URL: url.URL{Scheme: defScheme, Path: "/a/b"}}},
|
||||||
{target: "a//b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a//b"}},
|
{target: "a//b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a//b", URL: url.URL{Scheme: defScheme, Path: "/a//b"}}},
|
||||||
{target: "google.com", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "google.com"}},
|
{target: "google.com", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "google.com", URL: url.URL{Scheme: defScheme, Path: "/google.com"}}},
|
||||||
{target: "google.com/?a=b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "google.com/?a=b"}},
|
{target: "google.com/?a=b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "google.com/", URL: url.URL{Scheme: defScheme, Path: "/google.com/", RawQuery: "a=b"}}},
|
||||||
{target: "/unix/socket/address", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "/unix/socket/address"}},
|
{target: "/unix/socket/address", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "/unix/socket/address", URL: url.URL{Scheme: defScheme, Path: "//unix/socket/address"}}},
|
||||||
|
|
||||||
// An unregistered scheme is specified.
|
// An unregistered scheme is specified.
|
||||||
{target: "a:///", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:///"}},
|
{target: "a:///", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:///", URL: url.URL{Scheme: defScheme, Path: "/a:///"}}},
|
||||||
{target: "a://b/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b/"}},
|
{target: "a://b/", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b/", URL: url.URL{Scheme: defScheme, Path: "/a://b/"}}},
|
||||||
{target: "a:///b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:///b"}},
|
{target: "a:///b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:///b", URL: url.URL{Scheme: defScheme, Path: "/a:///b"}}},
|
||||||
{target: "a://b/c", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b/c"}},
|
{target: "a://b/c", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b/c", URL: url.URL{Scheme: defScheme, Path: "/a://b/c"}}},
|
||||||
{target: "a:b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:b"}},
|
{target: "a:b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:b", URL: url.URL{Scheme: defScheme, Path: "/a:b"}}},
|
||||||
{target: "a:/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:/b"}},
|
{target: "a:/b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a:/b", URL: url.URL{Scheme: defScheme, Path: "/a:/b"}}},
|
||||||
{target: "a://b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b"}},
|
{target: "a://b", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "a://b", URL: url.URL{Scheme: defScheme, Path: "/a://b"}}},
|
||||||
|
|
||||||
// A registered scheme is specified.
|
// A registered scheme is specified.
|
||||||
{target: "dns:///google.com", wantParsed: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "google.com"}},
|
{target: "dns:///google.com", wantParsed: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "google.com", URL: url.URL{Scheme: "dns", Path: "/google.com"}}},
|
||||||
{target: "dns://a.server.com/google.com", wantParsed: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com"}},
|
{target: "dns://a.server.com/google.com", wantParsed: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com", URL: url.URL{Scheme: "dns", Host: "a.server.com", Path: "/google.com"}}},
|
||||||
{target: "dns://a.server.com/google.com/?a=b", wantParsed: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/?a=b"}},
|
{target: "dns://a.server.com/google.com/?a=b", wantParsed: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/", URL: url.URL{Scheme: "dns", Host: "a.server.com", Path: "/google.com/", RawQuery: "a=b"}}},
|
||||||
{target: "unix:///a/b/c", wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/a/b/c"}},
|
{target: "unix:///a/b/c", wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix", Path: "/a/b/c"}}},
|
||||||
{target: "unix-abstract:a/b/c", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a/b/c"}},
|
{target: "unix-abstract:a/b/c", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "a/b/c"}}},
|
||||||
{target: "unix-abstract:a b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a b"}},
|
{target: "unix-abstract:a b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a b", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "a b"}}},
|
||||||
{target: "unix-abstract:a:b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a:b"}},
|
{target: "unix-abstract:a:b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a:b", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "a:b"}}},
|
||||||
{target: "unix-abstract:a-b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a-b"}},
|
{target: "unix-abstract:a-b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a-b", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "a-b"}}},
|
||||||
{target: "unix-abstract:/ a///://::!@#$%^&*()b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/ a///://::!@#$%^&*()b"}},
|
{target: "unix-abstract:/ a///://::!@#$%25^&*()b", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: " a///://::!@", URL: url.URL{Scheme: "unix-abstract", Path: "/ a///://::!@", RawPath: "/ a///://::!@", Fragment: "$%^&*()b", RawFragment: "$%25^&*()b"}}},
|
||||||
{target: "unix-abstract:passthrough:abc", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "passthrough:abc"}},
|
{target: "unix-abstract:passthrough:abc", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "passthrough:abc", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "passthrough:abc"}}},
|
||||||
{target: "unix-abstract:unix:///abc", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "unix:///abc"}},
|
{target: "unix-abstract:unix:///abc", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "unix:///abc", URL: url.URL{Scheme: "unix-abstract", Path: "", Opaque: "unix:///abc"}}},
|
||||||
{target: "unix-abstract:///a/b/c", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/a/b/c"}},
|
{target: "unix-abstract:///a/b/c", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix-abstract", Path: "/a/b/c"}}},
|
||||||
{target: "unix-abstract:///", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/"}},
|
{target: "unix-abstract:///", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "", URL: url.URL{Scheme: "unix-abstract", Path: "/"}}},
|
||||||
{target: "unix-abstract://authority", wantParsed: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "//authority"}},
|
{target: "passthrough:///unix:///a/b/c", wantParsed: resolver.Target{Scheme: "passthrough", Authority: "", Endpoint: "unix:///a/b/c", URL: url.URL{Scheme: "passthrough", Path: "/unix:///a/b/c"}}},
|
||||||
{target: "unix://domain", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "unix://domain"}},
|
|
||||||
{target: "passthrough:///unix:///a/b/c", wantParsed: resolver.Target{Scheme: "passthrough", Authority: "", Endpoint: "unix:///a/b/c"}},
|
// Cases for `scheme:absolute-path`.
|
||||||
|
{target: "dns:/a/b/c", wantParsed: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "dns", Path: "/a/b/c"}}},
|
||||||
|
{target: "unregistered:/a/b/c", wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "unregistered:/a/b/c", URL: url.URL{Scheme: defScheme, Path: "/unregistered:/a/b/c"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -84,8 +89,8 @@ func (s) TestParsedTarget_Success_WithoutCustomDialer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer cc.Close()
|
defer cc.Close()
|
||||||
|
|
||||||
if gotParsed := cc.parsedTarget; gotParsed != test.wantParsed {
|
if !cmp.Equal(cc.parsedTarget, test.wantParsed) {
|
||||||
t.Errorf("cc.parsedTarget = %+v, want %+v", gotParsed, test.wantParsed)
|
t.Errorf("cc.parsedTarget for dial target %q = %+v, want %+v", test.target, cc.parsedTarget, test.wantParsed)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -94,7 +99,9 @@ func (s) TestParsedTarget_Success_WithoutCustomDialer(t *testing.T) {
|
|||||||
func (s) TestParsedTarget_Failure_WithoutCustomDialer(t *testing.T) {
|
func (s) TestParsedTarget_Failure_WithoutCustomDialer(t *testing.T) {
|
||||||
targets := []string{
|
targets := []string{
|
||||||
"unix://a/b/c",
|
"unix://a/b/c",
|
||||||
|
"unix://authority",
|
||||||
"unix-abstract://authority/a/b/c",
|
"unix-abstract://authority/a/b/c",
|
||||||
|
"unix-abstract://authority",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
@ -118,39 +125,49 @@ func (s) TestParsedTarget_WithCustomDialer(t *testing.T) {
|
|||||||
// different behaviors with a custom dialer.
|
// different behaviors with a custom dialer.
|
||||||
{
|
{
|
||||||
target: "unix:a/b/c",
|
target: "unix:a/b/c",
|
||||||
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "unix:a/b/c"},
|
wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix", Opaque: "a/b/c"}},
|
||||||
wantDialerAddress: "unix:a/b/c",
|
wantDialerAddress: "unix:a/b/c",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "unix:/a/b/c",
|
target: "unix:/a/b/c",
|
||||||
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "unix:/a/b/c"},
|
wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix", Path: "/a/b/c"}},
|
||||||
wantDialerAddress: "unix:/a/b/c",
|
wantDialerAddress: "unix:///a/b/c",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "unix:///a/b/c",
|
target: "unix:///a/b/c",
|
||||||
wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/a/b/c"},
|
wantParsed: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "a/b/c", URL: url.URL{Scheme: "unix", Path: "/a/b/c"}},
|
||||||
wantDialerAddress: "unix:///a/b/c",
|
wantDialerAddress: "unix:///a/b/c",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "dns:///127.0.0.1:50051",
|
target: "dns:///127.0.0.1:50051",
|
||||||
wantParsed: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "127.0.0.1:50051"},
|
wantParsed: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "127.0.0.1:50051", URL: url.URL{Scheme: "dns", Path: "/127.0.0.1:50051"}},
|
||||||
wantDialerAddress: "127.0.0.1:50051",
|
wantDialerAddress: "127.0.0.1:50051",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: ":///127.0.0.1:50051",
|
target: ":///127.0.0.1:50051",
|
||||||
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///127.0.0.1:50051"},
|
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: ":///127.0.0.1:50051", URL: url.URL{Scheme: defScheme, Path: "/:///127.0.0.1:50051"}},
|
||||||
wantDialerAddress: ":///127.0.0.1:50051",
|
wantDialerAddress: ":///127.0.0.1:50051",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "dns://authority/127.0.0.1:50051",
|
target: "dns://authority/127.0.0.1:50051",
|
||||||
wantParsed: resolver.Target{Scheme: "dns", Authority: "authority", Endpoint: "127.0.0.1:50051"},
|
wantParsed: resolver.Target{Scheme: "dns", Authority: "authority", Endpoint: "127.0.0.1:50051", URL: url.URL{Scheme: "dns", Host: "authority", Path: "/127.0.0.1:50051"}},
|
||||||
wantDialerAddress: "127.0.0.1:50051",
|
wantDialerAddress: "127.0.0.1:50051",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
target: "://authority/127.0.0.1:50051",
|
target: "://authority/127.0.0.1:50051",
|
||||||
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://authority/127.0.0.1:50051"},
|
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "://authority/127.0.0.1:50051", URL: url.URL{Scheme: defScheme, Path: "/://authority/127.0.0.1:50051"}},
|
||||||
wantDialerAddress: "://authority/127.0.0.1:50051",
|
wantDialerAddress: "://authority/127.0.0.1:50051",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: "/unix/socket/address",
|
||||||
|
wantParsed: resolver.Target{Scheme: defScheme, Authority: "", Endpoint: "/unix/socket/address", URL: url.URL{Scheme: defScheme, Path: "//unix/socket/address"}},
|
||||||
|
wantDialerAddress: "/unix/socket/address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: "passthrough://a.server.com/google.com",
|
||||||
|
wantParsed: resolver.Target{Scheme: "passthrough", Authority: "a.server.com", Endpoint: "google.com", URL: url.URL{Scheme: "passthrough", Host: "a.server.com", Path: "/google.com"}},
|
||||||
|
wantDialerAddress: "google.com",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -175,8 +192,8 @@ func (s) TestParsedTarget_WithCustomDialer(t *testing.T) {
|
|||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatal("timeout when waiting for custom dialer to be invoked")
|
t.Fatal("timeout when waiting for custom dialer to be invoked")
|
||||||
}
|
}
|
||||||
if gotParsed := cc.parsedTarget; gotParsed != test.wantParsed {
|
if !cmp.Equal(cc.parsedTarget, test.wantParsed) {
|
||||||
t.Errorf("cc.parsedTarget for dial target %q = %+v, want %+v", test.target, gotParsed, test.wantParsed)
|
t.Errorf("cc.parsedTarget for dial target %q = %+v, want %+v", test.target, cc.parsedTarget, test.wantParsed)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,11 @@ type TransportCredentials interface {
|
|||||||
// Additionally, ClientHandshakeInfo data will be available via the context
|
// Additionally, ClientHandshakeInfo data will be available via the context
|
||||||
// passed to this call.
|
// passed to this call.
|
||||||
//
|
//
|
||||||
|
// The second argument to this method is the `:authority` header value used
|
||||||
|
// while creating new streams on this connection after authentication
|
||||||
|
// succeeds. Implementations must use this as the server name during the
|
||||||
|
// authentication handshake.
|
||||||
|
//
|
||||||
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
|
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
|
||||||
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
|
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
|
||||||
// ServerHandshake does the authentication handshake for servers. It returns
|
// ServerHandshake does the authentication handshake for servers. It returns
|
||||||
@ -153,9 +158,13 @@ type TransportCredentials interface {
|
|||||||
Info() ProtocolInfo
|
Info() ProtocolInfo
|
||||||
// Clone makes a copy of this TransportCredentials.
|
// Clone makes a copy of this TransportCredentials.
|
||||||
Clone() TransportCredentials
|
Clone() TransportCredentials
|
||||||
// OverrideServerName overrides the server name used to verify the hostname on the returned certificates from the server.
|
// OverrideServerName specifies the value used for the following:
|
||||||
// gRPC internals also use it to override the virtual hosting name if it is set.
|
// - verifying the hostname on the returned certificates
|
||||||
// It must be called before dialing. Currently, this is only used by grpclb.
|
// - as SNI in the client's handshake to support virtual hosting
|
||||||
|
// - as the value for `:authority` header at stream creation time
|
||||||
|
//
|
||||||
|
// Deprecated: use grpc.WithAuthority instead. Will be supported
|
||||||
|
// throughout 1.x.
|
||||||
OverrideServerName(string) error
|
OverrideServerName(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,8 +482,7 @@ func WithChainStreamInterceptor(interceptors ...StreamClientInterceptor) DialOpt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithAuthority returns a DialOption that specifies the value to be used as the
|
// WithAuthority returns a DialOption that specifies the value to be used as the
|
||||||
// :authority pseudo-header. This value only works with WithInsecure and has no
|
// :authority pseudo-header and as the server name in authentication handshake.
|
||||||
// effect if TransportCredentials are present.
|
|
||||||
func WithAuthority(a string) DialOption {
|
func WithAuthority(a string) DialOption {
|
||||||
return newFuncDialOption(func(o *dialOptions) {
|
return newFuncDialOption(func(o *dialOptions) {
|
||||||
o.authority = a
|
o.authority = a
|
||||||
|
20
internal/grpcutil/grpcutil.go
Normal file
20
internal/grpcutil/grpcutil.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2021 gRPC 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 grpcutil provides utility functions used across the gRPC codebase.
|
||||||
|
package grpcutil
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
*
|
|
||||||
* Copyright 2020 gRPC 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 grpcutil provides a bunch of utility functions to be used across the
|
|
||||||
// gRPC codebase.
|
|
||||||
package grpcutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// split2 returns the values from strings.SplitN(s, sep, 2).
|
|
||||||
// If sep is not found, it returns ("", "", false) instead.
|
|
||||||
func split2(s, sep string) (string, string, bool) {
|
|
||||||
spl := strings.SplitN(s, sep, 2)
|
|
||||||
if len(spl) < 2 {
|
|
||||||
return "", "", false
|
|
||||||
}
|
|
||||||
return spl[0], spl[1], true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseTarget splits target into a resolver.Target struct containing scheme,
|
|
||||||
// authority and endpoint. skipUnixColonParsing indicates that the parse should
|
|
||||||
// not parse "unix:[path]" cases. This should be true in cases where a custom
|
|
||||||
// dialer is present, to prevent a behavior change.
|
|
||||||
//
|
|
||||||
// If target is not a valid scheme://authority/endpoint as specified in
|
|
||||||
// https://github.com/grpc/grpc/blob/master/doc/naming.md,
|
|
||||||
// it returns {Endpoint: target}.
|
|
||||||
func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) {
|
|
||||||
var ok bool
|
|
||||||
if strings.HasPrefix(target, "unix-abstract:") {
|
|
||||||
if strings.HasPrefix(target, "unix-abstract://") {
|
|
||||||
// Maybe, with Authority specified, try to parse it
|
|
||||||
var remain string
|
|
||||||
ret.Scheme, remain, _ = split2(target, "://")
|
|
||||||
ret.Authority, ret.Endpoint, ok = split2(remain, "/")
|
|
||||||
if !ok {
|
|
||||||
// No Authority, add the "//" back
|
|
||||||
ret.Endpoint = "//" + remain
|
|
||||||
} else {
|
|
||||||
// Found Authority, add the "/" back
|
|
||||||
ret.Endpoint = "/" + ret.Endpoint
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Without Authority specified, split target on ":"
|
|
||||||
ret.Scheme, ret.Endpoint, _ = split2(target, ":")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
ret.Scheme, ret.Endpoint, ok = split2(target, "://")
|
|
||||||
if !ok {
|
|
||||||
if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing {
|
|
||||||
// Handle the "unix:[local/path]" and "unix:[/absolute/path]" cases,
|
|
||||||
// because splitting on :// only handles the
|
|
||||||
// "unix://[/absolute/path]" case. Only handle if the dialer is nil,
|
|
||||||
// to avoid a behavior change with custom dialers.
|
|
||||||
return resolver.Target{Scheme: "unix", Endpoint: target[len("unix:"):]}
|
|
||||||
}
|
|
||||||
return resolver.Target{Endpoint: target}
|
|
||||||
}
|
|
||||||
ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/")
|
|
||||||
if !ok {
|
|
||||||
return resolver.Target{Endpoint: target}
|
|
||||||
}
|
|
||||||
if ret.Scheme == "unix" {
|
|
||||||
// Add the "/" back in the unix case, so the unix resolver receives the
|
|
||||||
// actual endpoint in the "unix://[/absolute/path]" case.
|
|
||||||
ret.Endpoint = "/" + ret.Endpoint
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
@ -37,7 +37,17 @@ func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolv
|
|||||||
if target.Authority != "" {
|
if target.Authority != "" {
|
||||||
return nil, fmt.Errorf("invalid (non-empty) authority: %v", target.Authority)
|
return nil, fmt.Errorf("invalid (non-empty) authority: %v", target.Authority)
|
||||||
}
|
}
|
||||||
addr := resolver.Address{Addr: target.Endpoint}
|
|
||||||
|
// gRPC was parsing the dial target manually before PR #4817, and we
|
||||||
|
// switched to using url.Parse() in that PR. To avoid breaking existing
|
||||||
|
// resolver implementations we ended up stripping the leading "/" from the
|
||||||
|
// endpoint. This obviously does not work for the "unix" scheme. Hence we
|
||||||
|
// end up using the parsed URL instead.
|
||||||
|
endpoint := target.URL.Path
|
||||||
|
if endpoint == "" {
|
||||||
|
endpoint = target.URL.Opaque
|
||||||
|
}
|
||||||
|
addr := resolver.Address{Addr: endpoint}
|
||||||
if b.scheme == unixAbstractScheme {
|
if b.scheme == unixAbstractScheme {
|
||||||
// prepend "\x00" to address for unix-abstract
|
// prepend "\x00" to address for unix-abstract
|
||||||
addr.Addr = "\x00" + addr.Addr
|
addr.Addr = "\x00" + addr.Addr
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -146,13 +147,20 @@ func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error
|
|||||||
address := addr.Addr
|
address := addr.Addr
|
||||||
networkType, ok := networktype.Get(addr)
|
networkType, ok := networktype.Get(addr)
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
|
// Special handling for unix scheme with custom dialer. Back in the day,
|
||||||
|
// we did not have a unix resolver and therefore targets with a unix
|
||||||
|
// scheme would end up using the passthrough resolver. So, user's used a
|
||||||
|
// custom dialer in this case and expected the original dial target to
|
||||||
|
// be passed to the custom dialer. Now, we have a unix resolver. But if
|
||||||
|
// a custom dialer is specified, we want to retain the old behavior in
|
||||||
|
// terms of the address being passed to the custom dialer.
|
||||||
if networkType == "unix" && !strings.HasPrefix(address, "\x00") {
|
if networkType == "unix" && !strings.HasPrefix(address, "\x00") {
|
||||||
// For backward compatibility, if the user dialed "unix:///path",
|
// Supported unix targets are either "unix://absolute-path" or
|
||||||
// the passthrough resolver would be used and the user's custom
|
// "unix:relative-path".
|
||||||
// dialer would see "unix:///path". Since the unix resolver is used
|
if filepath.IsAbs(address) {
|
||||||
// and the address is now "/path", prepend "unix://" so the user's
|
return fn(ctx, "unix://"+address)
|
||||||
// custom dialer sees the same address.
|
}
|
||||||
return fn(ctx, "unix://"+address)
|
return fn(ctx, "unix:"+address)
|
||||||
}
|
}
|
||||||
return fn(ctx, address)
|
return fn(ctx, address)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ package resolver
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"google.golang.org/grpc/attributes"
|
"google.golang.org/grpc/attributes"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
@ -204,25 +205,36 @@ type ClientConn interface {
|
|||||||
|
|
||||||
// Target represents a target for gRPC, as specified in:
|
// Target represents a target for gRPC, as specified in:
|
||||||
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
|
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
|
||||||
// It is parsed from the target string that gets passed into Dial or DialContext by the user. And
|
// It is parsed from the target string that gets passed into Dial or DialContext
|
||||||
// grpc passes it to the resolver and the balancer.
|
// by the user. And gRPC passes it to the resolver and the balancer.
|
||||||
//
|
//
|
||||||
// If the target follows the naming spec, and the parsed scheme is registered with grpc, we will
|
// If the target follows the naming spec, and the parsed scheme is registered
|
||||||
// parse the target string according to the spec. e.g. "dns://some_authority/foo.bar" will be parsed
|
// with gRPC, we will parse the target string according to the spec. If the
|
||||||
// into &Target{Scheme: "dns", Authority: "some_authority", Endpoint: "foo.bar"}
|
// target does not contain a scheme or if the parsed scheme is not registered
|
||||||
|
// (i.e. no corresponding resolver available to resolve the endpoint), we will
|
||||||
|
// apply the default scheme, and will attempt to reparse it.
|
||||||
//
|
//
|
||||||
// If the target does not contain a scheme, we will apply the default scheme, and set the Target to
|
// Examples:
|
||||||
// be the full target string. e.g. "foo.bar" will be parsed into
|
|
||||||
// &Target{Scheme: resolver.GetDefaultScheme(), Endpoint: "foo.bar"}.
|
|
||||||
//
|
//
|
||||||
// If the parsed scheme is not registered (i.e. no corresponding resolver available to resolve the
|
// - "dns://some_authority/foo.bar"
|
||||||
// endpoint), we set the Scheme to be the default scheme, and set the Endpoint to be the full target
|
// Target{Scheme: "dns", Authority: "some_authority", Endpoint: "foo.bar"}
|
||||||
// string. e.g. target string "unknown_scheme://authority/endpoint" will be parsed into
|
// - "foo.bar"
|
||||||
// &Target{Scheme: resolver.GetDefaultScheme(), Endpoint: "unknown_scheme://authority/endpoint"}.
|
// Target{Scheme: resolver.GetDefaultScheme(), Endpoint: "foo.bar"}
|
||||||
|
// - "unknown_scheme://authority/endpoint"
|
||||||
|
// Target{Scheme: resolver.GetDefaultScheme(), Endpoint: "unknown_scheme://authority/endpoint"}
|
||||||
type Target struct {
|
type Target struct {
|
||||||
Scheme string
|
// Deprecated: use URL.Scheme instead.
|
||||||
|
Scheme string
|
||||||
|
// Deprecated: use URL.Host instead.
|
||||||
Authority string
|
Authority string
|
||||||
Endpoint string
|
// Deprecated: use URL.Path or URL.Opaque instead. The latter is set when
|
||||||
|
// the former is empty.
|
||||||
|
Endpoint string
|
||||||
|
// URL contains the parsed dial target with an optional default scheme added
|
||||||
|
// to it if the original dial target contained no scheme or contained an
|
||||||
|
// unregistered scheme. Any query params specified in the original dial
|
||||||
|
// target can be accessed from here.
|
||||||
|
URL url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder creates a resolver that will be used to watch name resolution updates.
|
// Builder creates a resolver that will be used to watch name resolution updates.
|
||||||
|
@ -21,8 +21,6 @@ package grpc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -36,37 +34,6 @@ import (
|
|||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The target string with unknown scheme should be kept unchanged and passed to
|
|
||||||
// the dialer.
|
|
||||||
func (s) TestDialParseTargetUnknownScheme(t *testing.T) {
|
|
||||||
for _, test := range []struct {
|
|
||||||
targetStr string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"/unix/socket/address", "/unix/socket/address"},
|
|
||||||
|
|
||||||
// For known scheme.
|
|
||||||
{"passthrough://a.server.com/google.com", "google.com"},
|
|
||||||
} {
|
|
||||||
dialStrCh := make(chan string, 1)
|
|
||||||
cc, err := Dial(test.targetStr, WithInsecure(), WithDialer(func(addr string, _ time.Duration) (net.Conn, error) {
|
|
||||||
select {
|
|
||||||
case dialStrCh <- addr:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("test dialer, always error")
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to create ClientConn: %v", err)
|
|
||||||
}
|
|
||||||
got := <-dialStrCh
|
|
||||||
cc.Close()
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("Dial(%q), dialer got %q, want %q", test.targetStr, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const happyBalancerName = "happy balancer"
|
const happyBalancerName = "happy balancer"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -103,10 +103,11 @@ var authorityTests = []authorityTest{
|
|||||||
authority: "localhost",
|
authority: "localhost",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UnixAbsolute",
|
name: "UnixAbsolute",
|
||||||
address: "/tmp/sock.sock",
|
address: "/tmp/sock.sock",
|
||||||
target: "unix:/tmp/sock.sock",
|
target: "unix:/tmp/sock.sock",
|
||||||
authority: "localhost",
|
authority: "localhost",
|
||||||
|
dialTargetWant: "unix:///tmp/sock.sock",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UnixAbsoluteAlternate",
|
name: "UnixAbsoluteAlternate",
|
||||||
|
@ -608,7 +608,7 @@ func (s) TestV2ClientWatchWithoutStream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer sCleanup()
|
defer sCleanup()
|
||||||
|
|
||||||
const scheme = "xds_client_test_whatever"
|
const scheme = "xds-client-test-whatever"
|
||||||
rb := manual.NewBuilderWithScheme(scheme)
|
rb := manual.NewBuilderWithScheme(scheme)
|
||||||
rb.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "no.such.server"}}})
|
rb.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: "no.such.server"}}})
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user