diff --git a/clientconn.go b/clientconn.go index b35ba94f292d..2841614abab0 100644 --- a/clientconn.go +++ b/clientconn.go @@ -270,7 +270,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn * 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:") { + } 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 diff --git a/internal/grpcutil/target.go b/internal/grpcutil/target.go index baa3479f12ca..8833021da02e 100644 --- a/internal/grpcutil/target.go +++ b/internal/grpcutil/target.go @@ -41,10 +41,30 @@ func split2(s, sep string) (string, string, bool) { // 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, it returns {Endpoint: -// target}. +// 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 { diff --git a/internal/grpcutil/target_test.go b/internal/grpcutil/target_test.go index 6083e84d75ae..f6c586dd0808 100644 --- a/internal/grpcutil/target_test.go +++ b/internal/grpcutil/target_test.go @@ -84,6 +84,18 @@ func TestParseTargetString(t *testing.T) { {targetStr: "unix:/a/b/c", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/a/b/c"}, wantWithDialer: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix:/a/b/c"}}, {targetStr: "unix:///a/b/c", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/a/b/c"}}, + {targetStr: "unix-abstract:a/b/c", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a/b/c"}}, + {targetStr: "unix-abstract:a b", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a b"}}, + {targetStr: "unix-abstract:a:b", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a:b"}}, + {targetStr: "unix-abstract:a-b", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "a-b"}}, + {targetStr: "unix-abstract:/ a///://::!@#$%^&*()b", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/ a///://::!@#$%^&*()b"}}, + {targetStr: "unix-abstract:passthrough:abc", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "passthrough:abc"}}, + {targetStr: "unix-abstract:unix:///abc", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "unix:///abc"}}, + {targetStr: "unix-abstract:///a/b/c", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/a/b/c"}}, + {targetStr: "unix-abstract://authority/a/b/c", want: resolver.Target{Scheme: "unix-abstract", Authority: "authority", Endpoint: "/a/b/c"}}, + {targetStr: "unix-abstract:///", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "/"}}, + {targetStr: "unix-abstract://authority", want: resolver.Target{Scheme: "unix-abstract", Authority: "", Endpoint: "//authority"}}, + {targetStr: "passthrough:///unix:///a/b/c", want: resolver.Target{Scheme: "passthrough", Authority: "", Endpoint: "unix:///a/b/c"}}, } { got := ParseTarget(test.targetStr, false) diff --git a/internal/resolver/unix/unix.go b/internal/resolver/unix/unix.go index 8e78d47197c8..0d5a811ddfad 100644 --- a/internal/resolver/unix/unix.go +++ b/internal/resolver/unix/unix.go @@ -26,20 +26,28 @@ import ( "google.golang.org/grpc/resolver" ) -const scheme = "unix" +const unixScheme = "unix" +const unixAbstractScheme = "unix-abstract" -type builder struct{} +type builder struct { + scheme string +} -func (*builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { +func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) { if target.Authority != "" { return nil, fmt.Errorf("invalid (non-empty) authority: %v", target.Authority) } - cc.UpdateState(resolver.State{Addresses: []resolver.Address{networktype.Set(resolver.Address{Addr: target.Endpoint}, "unix")}}) + addr := resolver.Address{Addr: target.Endpoint} + if b.scheme == unixAbstractScheme { + // prepend "\x00" to address for unix-abstract + addr.Addr = "\x00" + addr.Addr + } + cc.UpdateState(resolver.State{Addresses: []resolver.Address{networktype.Set(addr, "unix")}}) return &nopResolver{}, nil } -func (*builder) Scheme() string { - return scheme +func (b *builder) Scheme() string { + return b.scheme } type nopResolver struct { @@ -50,5 +58,6 @@ func (*nopResolver) ResolveNow(resolver.ResolveNowOptions) {} func (*nopResolver) Close() {} func init() { - resolver.Register(&builder{}) + resolver.Register(&builder{scheme: unixScheme}) + resolver.Register(&builder{scheme: unixAbstractScheme}) } diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 4c9f9740ec71..8902b7f90d9d 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -143,7 +143,7 @@ func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error address := addr.Addr networkType, ok := networktype.Get(addr) if fn != nil { - if networkType == "unix" { + if networkType == "unix" && !strings.HasPrefix(address, "\x00") { // For backward compatibility, if the user dialed "unix:///path", // the passthrough resolver would be used and the user's custom // dialer would see "unix:///path". Since the unix resolver is used diff --git a/test/authority_test.go b/test/authority_test.go index e6599b2fde04..f4e05cbaf875 100644 --- a/test/authority_test.go +++ b/test/authority_test.go @@ -23,6 +23,7 @@ import ( "fmt" "net" "os" + "strings" "sync" "testing" "time" @@ -53,8 +54,10 @@ func authorityChecker(ctx context.Context, expectedAuthority string) (*testpb.Em } func runUnixTest(t *testing.T, address, target, expectedAuthority string, dialer func(context.Context, string) (net.Conn, error)) { - if err := os.RemoveAll(address); err != nil { - t.Fatalf("Error removing socket file %v: %v\n", address, err) + if !strings.HasPrefix(target, "unix-abstract:") { + if err := os.RemoveAll(address); err != nil { + t.Fatalf("Error removing socket file %v: %v\n", address, err) + } } ss := &stubServer{ emptyCall: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) { @@ -114,6 +117,13 @@ var authorityTests = []authorityTest{ authority: "unix:///tmp/sock.sock", dialTargetWant: "unix:///tmp/sock.sock", }, + { + name: "UnixAbstract", + address: "\x00abc efg", + target: "unix-abstract:abc efg", + authority: "localhost", + dialTargetWant: "\x00abc efg", + }, } // TestUnix does end to end tests with the various supported unix target @@ -139,7 +149,9 @@ func (s) TestUnixCustomDialer(t *testing.T) { if address != test.dialTargetWant { return nil, fmt.Errorf("expected target %v in custom dialer, instead got %v", test.dialTargetWant, address) } - address = address[len("unix:"):] + if !strings.HasPrefix(test.target, "unix-abstract:") { + address = address[len("unix:"):] + } return (&net.Dialer{}).DialContext(ctx, "unix", address) } runUnixTest(t, test.address, test.target, test.authority, dialer)