support unix-abstract schema (#4079)

This commit is contained in:
Edmond 2020-12-23 00:56:49 +08:00 committed by GitHub
parent 666aea1fb3
commit 39a500abb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 14 deletions

View File

@ -270,7 +270,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
cc.authority = creds.Info().ServerName cc.authority = creds.Info().ServerName
} else if cc.dopts.insecure && cc.dopts.authority != "" { } else if cc.dopts.insecure && cc.dopts.authority != "" {
cc.authority = 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" cc.authority = "localhost"
} else if strings.HasPrefix(cc.parsedTarget.Endpoint, ":") { } else if strings.HasPrefix(cc.parsedTarget.Endpoint, ":") {
cc.authority = "localhost" + cc.parsedTarget.Endpoint cc.authority = "localhost" + cc.parsedTarget.Endpoint

View File

@ -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 // not parse "unix:[path]" cases. This should be true in cases where a custom
// dialer is present, to prevent a behavior change. // dialer is present, to prevent a behavior change.
// //
// If target is not a valid scheme://authority/endpoint, it returns {Endpoint: // If target is not a valid scheme://authority/endpoint as specified in
// target}. // https://github.com/grpc/grpc/blob/master/doc/naming.md,
// it returns {Endpoint: target}.
func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) { func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) {
var ok bool 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, "://") ret.Scheme, ret.Endpoint, ok = split2(target, "://")
if !ok { if !ok {
if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing { if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing {

View File

@ -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"}, 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:///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"}}, {targetStr: "passthrough:///unix:///a/b/c", want: resolver.Target{Scheme: "passthrough", Authority: "", Endpoint: "unix:///a/b/c"}},
} { } {
got := ParseTarget(test.targetStr, false) got := ParseTarget(test.targetStr, false)

View File

@ -26,20 +26,28 @@ import (
"google.golang.org/grpc/resolver" "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 != "" { 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)
} }
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 return &nopResolver{}, nil
} }
func (*builder) Scheme() string { func (b *builder) Scheme() string {
return scheme return b.scheme
} }
type nopResolver struct { type nopResolver struct {
@ -50,5 +58,6 @@ func (*nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
func (*nopResolver) Close() {} func (*nopResolver) Close() {}
func init() { func init() {
resolver.Register(&builder{}) resolver.Register(&builder{scheme: unixScheme})
resolver.Register(&builder{scheme: unixAbstractScheme})
} }

View File

@ -143,7 +143,7 @@ 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 {
if networkType == "unix" { if networkType == "unix" && !strings.HasPrefix(address, "\x00") {
// For backward compatibility, if the user dialed "unix:///path", // For backward compatibility, if the user dialed "unix:///path",
// the passthrough resolver would be used and the user's custom // the passthrough resolver would be used and the user's custom
// dialer would see "unix:///path". Since the unix resolver is used // dialer would see "unix:///path". Since the unix resolver is used

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -54,8 +55,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)) { func runUnixTest(t *testing.T, address, target, expectedAuthority string, dialer func(context.Context, string) (net.Conn, error)) {
if err := os.RemoveAll(address); err != nil { if !strings.HasPrefix(target, "unix-abstract:") {
t.Fatalf("Error removing socket file %v: %v\n", address, err) if err := os.RemoveAll(address); err != nil {
t.Fatalf("Error removing socket file %v: %v\n", address, err)
}
} }
ss := &stubserver.StubServer{ ss := &stubserver.StubServer{
EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) { EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
@ -115,6 +118,13 @@ var authorityTests = []authorityTest{
authority: "unix:///tmp/sock.sock", authority: "unix:///tmp/sock.sock",
dialTargetWant: "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 // TestUnix does end to end tests with the various supported unix target
@ -140,7 +150,9 @@ func (s) TestUnixCustomDialer(t *testing.T) {
if address != test.dialTargetWant { if address != test.dialTargetWant {
return nil, fmt.Errorf("expected target %v in custom dialer, instead got %v", test.dialTargetWant, address) 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) return (&net.Dialer{}).DialContext(ctx, "unix", address)
} }
runUnixTest(t, test.address, test.target, test.authority, dialer) runUnixTest(t, test.address, test.target, test.authority, dialer)