Skip to content

Commit 4be647f

Browse files
internal/resolver/unix: Implemented unix resolver. (grpc#3890)
1 parent ea47aa9 commit 4be647f

File tree

15 files changed

+229
-140
lines changed

15 files changed

+229
-140
lines changed

balancer/rls/internal/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (*rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig,
201201
if lookupService == "" {
202202
return nil, fmt.Errorf("rls: empty lookup_service in service config {%+v}", string(c))
203203
}
204-
parsedTarget := grpcutil.ParseTarget(lookupService)
204+
parsedTarget := grpcutil.ParseTarget(lookupService, false)
205205
if parsedTarget.Scheme == "" {
206206
parsedTarget.Scheme = resolver.GetDefaultScheme()
207207
}

clientconn.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"errors"
2424
"fmt"
2525
"math"
26-
"net"
2726
"reflect"
2827
"strings"
2928
"sync"
@@ -48,6 +47,7 @@ import (
4847
_ "google.golang.org/grpc/balancer/roundrobin" // To register roundrobin.
4948
_ "google.golang.org/grpc/internal/resolver/dns" // To register dns resolver.
5049
_ "google.golang.org/grpc/internal/resolver/passthrough" // To register passthrough resolver.
50+
_ "google.golang.org/grpc/internal/resolver/unix" // To register unix resolver.
5151
)
5252

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

194-
if cc.dopts.copts.Dialer == nil {
195-
cc.dopts.copts.Dialer = func(ctx context.Context, addr string) (net.Conn, error) {
196-
network, addr := parseDialTarget(addr)
197-
return (&net.Dialer{}).DialContext(ctx, network, addr)
198-
}
199-
if cc.dopts.withProxy {
200-
cc.dopts.copts.Dialer = newProxyDialer(cc.dopts.copts.Dialer)
201-
}
202-
}
203-
204194
if cc.dopts.copts.UserAgent != "" {
205195
cc.dopts.copts.UserAgent += " " + grpcUA
206196
} else {
@@ -244,8 +234,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
244234
}
245235

246236
// Determine the resolver to use.
247-
cc.parsedTarget = grpcutil.ParseTarget(cc.target)
248-
unixScheme := strings.HasPrefix(cc.target, "unix:")
237+
cc.parsedTarget = grpcutil.ParseTarget(cc.target, cc.dopts.copts.Dialer != nil)
249238
channelz.Infof(logger, cc.channelzID, "parsed scheme: %q", cc.parsedTarget.Scheme)
250239
resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)
251240
if resolverBuilder == nil {
@@ -268,7 +257,7 @@ func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *
268257
cc.authority = creds.Info().ServerName
269258
} else if cc.dopts.insecure && cc.dopts.authority != "" {
270259
cc.authority = cc.dopts.authority
271-
} else if unixScheme {
260+
} else if strings.HasPrefix(cc.target, "unix:") {
272261
cc.authority = "localhost"
273262
} else {
274263
// Use endpoint from "scheme://authority/endpoint" as the default

dialoptions.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ type dialOptions struct {
7171
// we need to be able to configure this in tests.
7272
resolveNowBackoff func(int) time.Duration
7373
resolvers []resolver.Builder
74-
withProxy bool
7574
}
7675

7776
// DialOption configures how we set up the connection.
@@ -325,7 +324,7 @@ func WithInsecure() DialOption {
325324
// later release.
326325
func WithNoProxy() DialOption {
327326
return newFuncDialOption(func(o *dialOptions) {
328-
o.withProxy = false
327+
o.copts.UseProxy = false
329328
})
330329
}
331330

@@ -595,9 +594,9 @@ func defaultDialOptions() dialOptions {
595594
copts: transport.ConnectOptions{
596595
WriteBufferSize: defaultWriteBufSize,
597596
ReadBufferSize: defaultReadBufSize,
597+
UseProxy: true,
598598
},
599599
resolveNowBackoff: internalbackoff.DefaultExponential.Backoff,
600-
withProxy: true,
601600
}
602601
}
603602

internal/grpcutil/target.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,32 @@ func split2(s, sep string) (string, string, bool) {
3737
}
3838

3939
// ParseTarget splits target into a resolver.Target struct containing scheme,
40-
// authority and endpoint.
40+
// authority and endpoint. skipUnixColonParsing indicates that the parse should
41+
// not parse "unix:[path]" cases. This should be true in cases where a custom
42+
// dialer is present, to prevent a behavior change.
4143
//
4244
// If target is not a valid scheme://authority/endpoint, it returns {Endpoint:
4345
// target}.
44-
func ParseTarget(target string) (ret resolver.Target) {
46+
func ParseTarget(target string, skipUnixColonParsing bool) (ret resolver.Target) {
4547
var ok bool
4648
ret.Scheme, ret.Endpoint, ok = split2(target, "://")
4749
if !ok {
50+
if strings.HasPrefix(target, "unix:") && !skipUnixColonParsing {
51+
// Handle the "unix:[path]" case, because splitting on :// only
52+
// handles the "unix://[/absolute/path]" case. Only handle if the
53+
// dialer is nil, to avoid a behavior change with custom dialers.
54+
return resolver.Target{Scheme: "unix", Endpoint: target[len("unix:"):]}
55+
}
4856
return resolver.Target{Endpoint: target}
4957
}
5058
ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/")
5159
if !ok {
5260
return resolver.Target{Endpoint: target}
5361
}
62+
if ret.Scheme == "unix" {
63+
// Add the "/" back in the unix case, so the unix resolver receives the
64+
// actual endpoint.
65+
ret.Endpoint = "/" + ret.Endpoint
66+
}
5467
return ret
5568
}

internal/grpcutil/target_test.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,22 @@ func TestParseTarget(t *testing.T) {
3232
{Scheme: "passthrough", Authority: "", Endpoint: "/unix/socket/address"},
3333
} {
3434
str := test.Scheme + "://" + test.Authority + "/" + test.Endpoint
35-
got := ParseTarget(str)
35+
got := ParseTarget(str, false)
3636
if got != test {
37-
t.Errorf("ParseTarget(%q) = %+v, want %+v", str, got, test)
37+
t.Errorf("ParseTarget(%q, false) = %+v, want %+v", str, got, test)
38+
}
39+
got = ParseTarget(str, true)
40+
if got != test {
41+
t.Errorf("ParseTarget(%q, true) = %+v, want %+v", str, got, test)
3842
}
3943
}
4044
}
4145

4246
func TestParseTargetString(t *testing.T) {
4347
for _, test := range []struct {
44-
targetStr string
45-
want resolver.Target
48+
targetStr string
49+
want resolver.Target
50+
wantWithDialer resolver.Target
4651
}{
4752
{targetStr: "", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
4853
{targetStr: ":///", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}},
@@ -70,10 +75,24 @@ func TestParseTargetString(t *testing.T) {
7075
{targetStr: "a:/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:/b"}},
7176
{targetStr: "a//b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a//b"}},
7277
{targetStr: "a://b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a://b"}},
78+
79+
// Unix cases without custom dialer.
80+
// unix:[local_path] and unix:[/absolute] have different behaviors with
81+
// a custom dialer, to prevent behavior changes with custom dialers.
82+
{targetStr: "unix:domain", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "domain"}, wantWithDialer: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix:domain"}},
83+
{targetStr: "unix:/domain", want: resolver.Target{Scheme: "unix", Authority: "", Endpoint: "/domain"}, wantWithDialer: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix:/domain"}},
7384
} {
74-
got := ParseTarget(test.targetStr)
85+
got := ParseTarget(test.targetStr, false)
7586
if got != test.want {
76-
t.Errorf("ParseTarget(%q) = %+v, want %+v", test.targetStr, got, test.want)
87+
t.Errorf("ParseTarget(%q, false) = %+v, want %+v", test.targetStr, got, test.want)
88+
}
89+
wantWithDialer := test.wantWithDialer
90+
if wantWithDialer == (resolver.Target{}) {
91+
wantWithDialer = test.want
92+
}
93+
got = ParseTarget(test.targetStr, true)
94+
if got != wantWithDialer {
95+
t.Errorf("ParseTarget(%q, true) = %+v, want %+v", test.targetStr, got, wantWithDialer)
7796
}
7897
}
7998
}

internal/resolver/unix/unix.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
*
3+
* Copyright 2020 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
// Package unix implements a resolver for unix targets.
20+
package unix
21+
22+
import (
23+
"google.golang.org/grpc/internal/transport/networktype"
24+
"google.golang.org/grpc/resolver"
25+
)
26+
27+
const scheme = "unix"
28+
29+
type builder struct{}
30+
31+
func (*builder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
32+
cc.UpdateState(resolver.State{Addresses: []resolver.Address{networktype.Set(resolver.Address{Addr: target.Endpoint}, "unix")}})
33+
return &nopResolver{}, nil
34+
}
35+
36+
func (*builder) Scheme() string {
37+
return scheme
38+
}
39+
40+
type nopResolver struct {
41+
}
42+
43+
func (*nopResolver) ResolveNow(resolver.ResolveNowOptions) {}
44+
45+
func (*nopResolver) Close() {}
46+
47+
func init() {
48+
resolver.Register(&builder{})
49+
}

internal/transport/http2_client.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"golang.org/x/net/http2"
3434
"golang.org/x/net/http2/hpack"
3535
"google.golang.org/grpc/internal/grpcutil"
36+
"google.golang.org/grpc/internal/transport/networktype"
3637

3738
"google.golang.org/grpc/codes"
3839
"google.golang.org/grpc/credentials"
@@ -137,11 +138,18 @@ type http2Client struct {
137138
connectionID uint64
138139
}
139140

140-
func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr string) (net.Conn, error) {
141+
func dial(ctx context.Context, fn func(context.Context, string) (net.Conn, error), addr resolver.Address, useProxy bool, grpcUA string) (net.Conn, error) {
141142
if fn != nil {
142-
return fn(ctx, addr)
143+
return fn(ctx, addr.Addr)
143144
}
144-
return (&net.Dialer{}).DialContext(ctx, "tcp", addr)
145+
networkType := "tcp"
146+
if n, ok := networktype.Get(addr); ok {
147+
networkType = n
148+
}
149+
if networkType == "tcp" && useProxy {
150+
return proxyDial(ctx, addr.Addr, grpcUA)
151+
}
152+
return (&net.Dialer{}).DialContext(ctx, networkType, addr.Addr)
145153
}
146154

147155
func isTemporary(err error) bool {
@@ -172,7 +180,7 @@ func newHTTP2Client(connectCtx, ctx context.Context, addr resolver.Address, opts
172180
}
173181
}()
174182

175-
conn, err := dial(connectCtx, opts.Dialer, addr.Addr)
183+
conn, err := dial(connectCtx, opts.Dialer, addr, opts.UseProxy, opts.UserAgent)
176184
if err != nil {
177185
if opts.FailOnNonTempDialError {
178186
return nil, connectionErrorf(isTemporary(err), err, "transport: error while dialing: %v", err)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* Copyright 2020 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
// Package networktype declares the network type to be used in the default
20+
// dailer. Attribute of a resolver.Address.
21+
package networktype
22+
23+
import (
24+
"google.golang.org/grpc/resolver"
25+
)
26+
27+
// keyType is the key to use for storing State in Attributes.
28+
type keyType string
29+
30+
const key = keyType("grpc.internal.transport.networktype")
31+
32+
// Set returns a copy of the provided address with attributes containing networkType.
33+
func Set(address resolver.Address, networkType string) resolver.Address {
34+
address.Attributes = address.Attributes.WithValues(key, networkType)
35+
return address
36+
}
37+
38+
// Get returns the network type in the resolver.Address and true, or "", false
39+
// if not present.
40+
func Get(address resolver.Address) (string, bool) {
41+
v := address.Attributes.Value(key)
42+
if v == nil {
43+
return "", false
44+
}
45+
return v.(string), true
46+
}

0 commit comments

Comments
 (0)