From 0bc0d3b7ead8a8bc20c1a8c3c72415721c0a050a Mon Sep 17 00:00:00 2001 From: Eryu Guan <45746212+eryugey@users.noreply.github.com> Date: Mon, 16 May 2022 14:34:27 +0800 Subject: [PATCH] feat: add vsock network type support (#1303) So we support connecting dfdaemon running on host outside of VM via virtio-vsock device. Signed-off-by: Eryu Guan --- go.mod | 2 + go.sum | 4 ++ pkg/dfnet/dfnet.go | 15 ++++-- pkg/rpc/dfdaemon/client/client.go | 7 ++- pkg/rpc/vsock.go | 79 +++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 pkg/rpc/vsock.go diff --git a/go.mod b/go.mod index 5cbe8771f..88d9a73fe 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/jarcoal/httpmock v1.0.8 github.com/looplab/fsm v0.3.0 github.com/mcuadros/go-gin-prometheus v0.1.0 + github.com/mdlayher/vsock v1.1.1 github.com/mitchellh/mapstructure v1.4.1 github.com/montanaflynn/stats v0.6.6 github.com/onsi/ginkgo/v2 v2.1.0 @@ -155,6 +156,7 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mdlayher/socket v0.2.0 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go.sum b/go.sum index d957610a3..114d328d1 100644 --- a/go.sum +++ b/go.sum @@ -642,6 +642,10 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mcuadros/go-gin-prometheus v0.1.0 h1:JNoWKvw/u9tyRJ8BL9ZJvfiXU8IHUw8gCvcf/5L8tnI= github.com/mcuadros/go-gin-prometheus v0.1.0/go.mod h1:ezECAsiHtCRIa+6Ii8THg7G7RJvpO4S19d499UkEE3s= +github.com/mdlayher/socket v0.2.0 h1:EY4YQd6hTAg2tcXF84p5DTHazShE50u5HeBzBaNgjkA= +github.com/mdlayher/socket v0.2.0/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= +github.com/mdlayher/vsock v1.1.1 h1:8lFuiXQnmICBrCIIA9PMgVSke6Fg6V4+r0v7r55k88I= +github.com/mdlayher/vsock v1.1.1/go.mod h1:Y43jzcy7KM3QB+/FK15pfqGxDMCMzUXWegEfIbSM18U= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= diff --git a/pkg/dfnet/dfnet.go b/pkg/dfnet/dfnet.go index 732f6ae93..cc093babc 100644 --- a/pkg/dfnet/dfnet.go +++ b/pkg/dfnet/dfnet.go @@ -26,8 +26,13 @@ import ( type NetworkType string const ( - TCP NetworkType = "tcp" - UNIX NetworkType = "unix" + TCP NetworkType = "tcp" + UNIX NetworkType = "unix" + VSOCK NetworkType = "vsock" + + TCPEndpointPrefix string = "dns:///" + UnixEndpointPrefix string = "unix://" + VsockEndpointPrefix string = "vsock://" ) type NetAddr struct { @@ -39,9 +44,11 @@ type NetAddr struct { func (n NetAddr) GetEndpoint() string { switch n.Type { case UNIX: - return "unix://" + n.Addr + return UnixEndpointPrefix + n.Addr + case VSOCK: + return VsockEndpointPrefix + n.Addr default: - return "dns:///" + n.Addr + return TCPEndpointPrefix + n.Addr } } diff --git a/pkg/rpc/dfdaemon/client/client.go b/pkg/rpc/dfdaemon/client/client.go index 560f7d350..60fb3387e 100644 --- a/pkg/rpc/dfdaemon/client/client.go +++ b/pkg/rpc/dfdaemon/client/client.go @@ -41,10 +41,15 @@ func GetClientByAddr(addrs []dfnet.NetAddr, opts ...grpc.DialOption) (DaemonClie if len(addrs) == 0 { return nil, errors.New("address list of daemon is empty") } + + dialOpts, err := rpc.VsockDialerOption(addrs, opts) + if err != nil { + return nil, err + } dc := &daemonClient{ rpc.NewConnection(context.Background(), "daemon-static", addrs, []rpc.ConnOption{ rpc.WithConnExpireTime(60 * time.Second), - rpc.WithDialOption(opts), + rpc.WithDialOption(dialOpts), }), } return dc, nil diff --git a/pkg/rpc/vsock.go b/pkg/rpc/vsock.go new file mode 100644 index 000000000..9a13c3a23 --- /dev/null +++ b/pkg/rpc/vsock.go @@ -0,0 +1,79 @@ +/* + * Copyright 2022 The Dragonfly 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 rpc + +import ( + "context" + "fmt" + "net" + "strconv" + "strings" + + "github.com/mdlayher/vsock" + "github.com/pkg/errors" + "google.golang.org/grpc" + + "d7y.io/dragonfly/v2/pkg/dfnet" +) + +// VsockDialer is the dialer for vsock, it expects `address` to be in dfnet.NetAddr.GetEndpoint() +// format, that is "vsock://cid:port" +func VsockDialer(_ctx context.Context, address string) (net.Conn, error) { + addrStr := strings.TrimPrefix(address, dfnet.VsockEndpointPrefix) + addr := strings.Split(addrStr, ":") + if len(addr) != 2 { + return nil, fmt.Errorf("invalid vsock address (%s), expected %scid:port", address, dfnet.VsockEndpointPrefix) + } + + cid, err := strconv.ParseUint(addr[0], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "failed to convert %q to vsock cid", addr[0]) + } + port, err := strconv.ParseUint(addr[1], 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "failed to convert %q to vsock port", addr[1]) + } + + conn, err := vsock.Dial(uint32(cid), uint32(port), nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to dial vsock %v:%v, address %s", uint32(cid), uint32(port), address) + } + return conn, nil +} + +// If `addrs` are all vsock addresses, add rpc.VsockDialer to DialOption, and return error if addrs +// have mixed vsock and other connection types. +func VsockDialerOption(addrs []dfnet.NetAddr, opts []grpc.DialOption) ([]grpc.DialOption, error) { + var prevType dfnet.NetworkType + hasVsock := false + for n, a := range addrs { + if a.Type != dfnet.VSOCK { + prevType = a.Type + continue + } + hasVsock = true + if n > 0 && prevType != dfnet.VSOCK { + return nil, fmt.Errorf("addrs(%v) have mixed vsock and other types", addrs) + } + prevType = a.Type + } + dialOpts := opts + if hasVsock { + dialOpts = append(opts, grpc.WithContextDialer(VsockDialer)) + } + return dialOpts, nil +}