Skip to content

Commit

Permalink
internal/resolver/unix: Implemented unix resolver. (#3890)
Browse files Browse the repository at this point in the history
  • Loading branch information
GarrettGutierrez1 committed Oct 16, 2020
1 parent ea47aa9 commit 4be647f
Show file tree
Hide file tree
Showing 15 changed files with 229 additions and 140 deletions.
2 changes: 1 addition & 1 deletion balancer/rls/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (*rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig,
if lookupService == "" {
return nil, fmt.Errorf("rls: empty lookup_service in service config {%+v}", string(c))
}
parsedTarget := grpcutil.ParseTarget(lookupService)
parsedTarget := grpcutil.ParseTarget(lookupService, false)
if parsedTarget.Scheme == "" {
parsedTarget.Scheme = resolver.GetDefaultScheme()
}
Expand Down
17 changes: 3 additions & 14 deletions clientconn.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"errors"
"fmt"
"math"
"net"
"reflect"
"strings"
"sync"
Expand All @@ -48,6 +47,7 @@ import (
_ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin.
_ "google.golang.org/grpc/internal/resolver/dns" // To register dns resolver.
_ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver.
_ "google.golang.org/grpc/internal/resolver/unix" // To register unix resolver.
)

const (
Expand Down Expand Up @@ -191,16 +191,6 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
}
cc.mkp = cc.dopts.copts.KeepaliveParams

if cc.dopts.copts.Dialer == nil {
cc.dopts.copts.Dialer = func(ctx context.Context, addr string) (net.Conn, error) {
network, addr := parseDialTarget(addr)
return (&net.Dialer{}).DialContext(ctx, network, addr)
}
if cc.dopts.withProxy {
cc.dopts.copts.Dialer = newProxyDialer(cc.dopts.copts.Dialer)
}
}

if cc.dopts.copts.UserAgent != "" {
cc.dopts.copts.UserAgent += " " + grpcUA
} else {
Expand Down Expand Up @@ -244,8 +234,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
}

// Determine the resolver to use.
cc.parsedTarget = grpcutil.ParseTarget(cc.target)
unixScheme := strings.HasPrefix(cc.target, "unix:")
cc.parsedTarget = grpcutil.ParseTarget(cc.target, cc.dopts.copts.Dialer != nil)
channelz.Infof(logger, cc.channelzID, "parsed scheme: %q", cc.parsedTarget.Scheme)
resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)
if resolverBuilder == nil {
Expand All @@ -268,7 +257,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 unixScheme {
} else if strings.HasPrefix(cc.target, "unix:") {
cc.authority = "localhost"
} else {
// Use endpoint from "scheme:https://authority/endpoint" as the default
Expand Down
5 changes: 2 additions & 3 deletions dialoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ type dialOptions struct {
// we need to be able to configure this in tests.
resolveNowBackoff func(int) time.Duration
resolvers []resolver.Builder
withProxy bool
}

// DialOption configures how we set up the connection.
Expand Down Expand Up @@ -325,7 +324,7 @@ func WithInsecure() DialOption {
// later release.
func WithNoProxy() DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.withProxy = false
o.copts.UseProxy = false
})
}

Expand Down Expand Up @@ -595,9 +594,9 @@ func defaultDialOptions() dialOptions {
copts: transport.ConnectOptions{
WriteBufferSize: defaultWriteBufSize,
ReadBufferSize: defaultReadBufSize,
UseProxy: true,
},
resolveNowBackoff: internalbackoff.DefaultExponential.Backoff,
withProxy: true,
}
}

Expand Down
17 changes: 15 additions & 2 deletions internal/grpcutil/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,32 @@ func split2(s, sep string) (string, string, bool) {
}

// ParseTarget splits target into a resolver.Target struct containing scheme,
// authority and endpoint.
// 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:https://authority/endpoint, it returns {Endpoint:
// target}.
func ParseTarget(target string) (ret resolver.Target) {
func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) {
var ok bool
ret.Scheme, ret.Endpoint, ok = split2(target, ":https://")
if !ok {
if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing {
// Handle the "unix:[path]" case, because splitting on :https:// only
// handles the "unix:https://[/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.
ret.Endpoint = "/" + ret.Endpoint
}
return ret
}
31 changes: 25 additions & 6 deletions internal/grpcutil/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,22 @@ func TestParseTarget(t *testing.T) {
{Scheme: "passthrough", Authority: "", Endpoint: "/unix/socket/address"},
} {
str := test.Scheme + ":https://" + test.Authority + "/" + test.Endpoint
got := ParseTarget(str)
got := ParseTarget(str, false)
if got != test {
t.Errorf("ParseTarget(%q) = %+v, want %+v", str, got, test)
t.Errorf("ParseTarget(%q, false) = %+v, want %+v", str, got, test)
}
got = ParseTarget(str, true)
if got != test {
t.Errorf("ParseTarget(%q, true) = %+v, want %+v", str, got, test)
}
}
}

func TestParseTargetString(t *testing.T) {
for _, test := range []struct {
targetStr string
want resolver.Target
targetStr string
want resolver.Target
wantWithDialer resolver.Target
}{
{targetStr: "", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
{targetStr: ":https:///", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
Expand Down Expand Up @@ -70,10 +75,24 @@ func TestParseTargetString(t *testing.T) {
{targetStr: "a:/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:/b"}},
{targetStr: "a//b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a//b"}},
{targetStr: "a:https://b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:https://b"}},

// Unix cases without custom dialer.
// unix:[local_path] and unix:[/absolute] have different behaviors with
// a custom dialer, to prevent behavior changes with custom dialers.
{targetStr: "unix:domain", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "domain"}, wantWithDialer: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix:domain"}},
{targetStr: "unix:/domain", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/domain"}, wantWithDialer: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix:/domain"}},
} {
got := ParseTarget(test.targetStr)
got := ParseTarget(test.targetStr, false)
if got != test.want {
t.Errorf("ParseTarget(%q) = %+v, want %+v", test.targetStr, got, test.want)
t.Errorf("ParseTarget(%q, false) = %+v, want %+v", test.targetStr, got, test.want)
}
wantWithDialer := test.wantWithDialer
if wantWithDialer == (resolver.Target{}) {
wantWithDialer = test.want
}
got = ParseTarget(test.targetStr, true)
if got != wantWithDialer {
t.Errorf("ParseTarget(%q, true) = %+v, want %+v", test.targetStr, got, wantWithDialer)
}
}
}
49 changes: 49 additions & 0 deletions internal/resolver/unix/unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
*
* 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:https://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 unix implements a resolver for unix targets.
package unix

import (
"google.golang.org/grpc/internal/transport/networktype"
"google.golang.org/grpc/resolver"
)

const scheme = "unix"

type builder struct{}

func (*builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
cc.UpdateState(resolver.State{Addresses: []resolver.Address{networktype.Set(resolver.Address{Addr: target.Endpoint}, "unix")}})
return &nopResolver{}, nil
}

func (*builder) Scheme() string {
return scheme
}

type nopResolver struct {
}

func (*nopResolver) ResolveNow(resolver.ResolveNowOptions) {}

func (*nopResolver) Close() {}

func init() {
resolver.Register(&builder{})
}
16 changes: 12 additions & 4 deletions internal/transport/http2_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
"google.golang.org/grpc/internal/grpcutil"
"google.golang.org/grpc/internal/transport/networktype"

"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
Expand Down Expand Up @@ -137,11 +138,18 @@ type http2Client struct {
connectionID uint64
}

func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr string) (net.Conn, error) {
func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr resolver.Address, useProxy bool, grpcUA string) (net.Conn, error) {
if fn != nil {
return fn(ctx, addr)
return fn(ctx, addr.Addr)
}
return (&net.Dialer{}).DialContext(ctx, "tcp", addr)
networkType := "tcp"
if n, ok := networktype.Get(addr); ok {
networkType = n
}
if networkType == "tcp" && useProxy {
return proxyDial(ctx, addr.Addr, grpcUA)
}
return (&net.Dialer{}).DialContext(ctx, networkType, addr.Addr)
}

func isTemporary(err error) bool {
Expand Down Expand Up @@ -172,7 +180,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
}
}()

conn, err := dial(connectCtx, opts.Dialer, addr.Addr)
conn, err := dial(connectCtx, opts.Dialer, addr, opts.UseProxy, opts.UserAgent)
if err != nil {
if opts.FailOnNonTempDialError {
return nil, connectionErrorf(isTemporary(err), err, "transport: error while dialing: %v", err)
Expand Down
46 changes: 46 additions & 0 deletions internal/transport/networktype/networktype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
*
* 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:https://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 networktype declares the network type to be used in the default
// dailer. Attribute of a resolver.Address.
package networktype

import (
"google.golang.org/grpc/resolver"
)

// keyType is the key to use for storing State in Attributes.
type keyType string

const key = keyType("grpc.internal.transport.networktype")

// Set returns a copy of the provided address with attributes containing networkType.
func Set(address resolver.Address, networkType string) resolver.Address {
address.Attributes = address.Attributes.WithValues(key, networkType)
return address
}

// Get returns the network type in the resolver.Address and true, or "", false
// if not present.
func Get(address resolver.Address) (string, bool) {
v := address.Attributes.Value(key)
if v == nil {
return "", false
}
return v.(string), true
}
Loading

0 comments on commit 4be647f

Please sign in to comment.