Skip to content

Commit

Permalink
Merge branch 'update-proto'
Browse files Browse the repository at this point in the history
  • Loading branch information
jxx-gg committed Mar 1, 2021
2 parents 8342cf1 + 4190afb commit 55444bc
Show file tree
Hide file tree
Showing 10 changed files with 512 additions and 253 deletions.
22 changes: 7 additions & 15 deletions cmd/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,31 +155,23 @@ func Run(ctx context.Context, conf Config) error {

errCh := make(chan error, 2)
go func() {
err := client.Run(ctx)
if err != nil {
if err := client.Register(ctx); err != nil {
errCh <- err
return
}
client.Start(ctx)
if err := client.Error(); err != nil {
errCh <- err
}
}()

// listen for any request to create a new session
cancels := make(map[tunnel.Target]func())
select {
case target := <-peerAddCh:
ctx, cancels[target] = context.WithCancel(ctx)
if err := listen(ctx, client, conf.ListenAddress, target); err != nil {
errCh <- err
cancels[target]()
}
case target := <-peerDelCh:
cancels[target]()
delete(cancels, target)
}

select {
return listen(ctx, client, conf.ListenAddress, target)
case <-ctx.Done():
return ctx.Err()
case err := <-errCh:
return err
}

}
2 changes: 1 addition & 1 deletion compile_protos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ set -euo pipefail
proto_imports=".:${GOPATH}/src/github.com/google/protobuf/src:${GOPATH}/src"

# Go
protoc -I=$proto_imports --go_out=plugins=grpc,paths=source_relative:. proto/tunnel/tunnel.proto
protoc -I=$proto_imports --go_out=paths=source_relative:. proto/tunnel/tunnel.proto --go-grpc_out=.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/openconfig/grpctunnel
go 1.14

require (
github.com/cenkalti/backoff/v4 v4.1.0 // indirect
github.com/golang/protobuf v1.4.3
google.golang.org/grpc v1.34.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
Expand Down
264 changes: 137 additions & 127 deletions proto/tunnel/tunnel.pb.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions proto/tunnel/tunnel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
// limitations under the License.
//

// Version: 0.1

syntax = "proto3";

// Package grpctunnel defines a service specification for a TCP over gRPC proxy
Expand Down Expand Up @@ -50,8 +52,12 @@ enum TargetType {
UNKNOWN = 0;
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=22
SSH = 22;
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=6653
OPENFLOW = 6653;
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=9339
GNMI_GNOI = 9339;
// https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=9559
P4_RUNTIME = 9559;
}

message Target {
Expand Down
8 changes: 5 additions & 3 deletions proto/tunnel/tunnel_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

211 changes: 211 additions & 0 deletions tunnel/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package tunnel

import (
"context"
"fmt"
"io"
"log"
"net"
"strings"
"time"

"github.com/cenkalti/backoff/v4"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

tpb "github.com/openconfig/grpctunnel/proto/tunnel"
)

var (
// RetryBaseDelay is the initial retry interval for re-connecting tunnel server/client.
RetryBaseDelay = time.Second
// RetryMaxDelay caps the retry interval for re-connecting attempts.
RetryMaxDelay = time.Minute
// RetryRandomization is the randomization factor applied to the retry
// interval.
RetryRandomization = 0.5
)

// Conn is a wraper as a net.Conn interface.
type Conn struct {
io.ReadWriteCloser
}

// LocalAddr is trivial implementation, in order to match interface net.Conn.
func (tc *Conn) LocalAddr() net.Addr { return nil }

// RemoteAddr is trivial implementation, in order to match interface net.Conn.
func (tc *Conn) RemoteAddr() net.Addr { return nil }

// SetDeadline is trivial implementation, in order to match interface net.Conn.
func (tc *Conn) SetDeadline(t time.Time) error { return nil }

// SetReadDeadline is trivial implementation, in order to match interface net.Conn.
func (tc *Conn) SetReadDeadline(t time.Time) error { return nil }

// SetWriteDeadline is trivial implementation, in order to match interface net.Conn.
func (tc *Conn) SetWriteDeadline(t time.Time) error { return nil }

// ServerConn (re-)tries and returns a tunnel connection.
func ServerConn(ctx context.Context, ts *Server, addr string, target *Target) (*Conn, error) {
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 0 // Retry Subscribe indefinitely.
bo.InitialInterval = RetryBaseDelay
bo.MaxInterval = RetryMaxDelay
bo.RandomizationFactor = RetryRandomization

for {
session, err := ts.NewSession(ctx, ServerSession{Target: *target})
if err == nil {
return &Conn{session}, nil
}
duration := bo.NextBackOff()
log.Printf("Failed to get tunnel connection: %v.\nRetrying in %s.", err, duration)
time.Sleep(duration)

select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}

}

}

func registerTunnelClient(ctx context.Context, addr string, cert string, l *Listener,
targets map[Target]struct{}) (*Client, error) {
opts := []grpc.DialOption{grpc.WithDefaultCallOptions()}
if cert == "" {
opts = append(opts, grpc.WithInsecure())
} else {
creds, err := credentials.NewClientTLSFromFile(cert, "")
if err != nil {
return nil, fmt.Errorf("failed to load credentials: %v", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
}
var err error
l.cc, err = grpc.Dial(addr, opts...)
if err != nil {
return nil, fmt.Errorf("grpc dial error: %v", err)
}

registerHandler := func(t Target) error {
if _, ok := targets[t]; !ok {
return fmt.Errorf("client cannot handle target ID: %s, type: %s", t.ID, t.Type)
}
log.Printf("register handler received id: %v, type: %v", t.ID, t.Type)
return nil
}

handler := func(t Target, i io.ReadWriteCloser) error {
log.Printf("handler called for id: %v, type: %v", t.ID, t.Type)
l.chIO <- i
return nil
}

client, err := NewClient(tpb.NewTunnelClient(l.cc), ClientConfig{
RegisterHandler: registerHandler,
Handler: handler,
}, targets)
if err != nil {
return nil, fmt.Errorf("failed to create tunnel client: %v", err)
}
if err := client.Register(ctx); err != nil {
return nil, err
}
return client, nil
}

// Listener wraps a tunnel connection.
type Listener struct {
conn []io.ReadWriteCloser
addr tunnelAddr
chIO chan io.ReadWriteCloser
chErr chan error
cc *grpc.ClientConn
}

// Accept waits and returns a tunnel connection.
func (l *Listener) Accept() (net.Conn, error) {
select {
case err := <-l.chErr:
return nil, fmt.Errorf("failed to get tunnel listener: %v", err)
case conn := <-l.chIO:
l.conn = append(l.conn, conn)
log.Printf("tunnel listen setup")
return &Conn{conn}, nil
}
}

// Close close the embedded connection. Will need more implementation to handle multiple connections.
func (l *Listener) Close() error {
var errs []string
if l.cc != nil {
if e := l.cc.Close(); e != nil {
errs = append(errs, fmt.Sprintf("failed to close listener.cc: %v", e))
}
}

if l.conn != nil {
for _, conn := range l.conn {
if e := conn.Close(); e != nil {
errs = append(errs, fmt.Sprintf("failed to close listener.conn: %v", e))
}
}
}
if len(errs) > 0 {
return fmt.Errorf(strings.Join(errs, "\n"))
}
return nil
}

// Addr is a trivial implementation.
func (l *Listener) Addr() net.Addr { return l.addr }

type tunnelAddr struct {
network string
address string
}

func (a tunnelAddr) Network() string { return a.network }
func (a tunnelAddr) String() string { return a.address }

// Listen create a tunnel client and returns a Listener.
func Listen(ctx context.Context, addr string, cert string, targets map[Target]struct{}) (net.Listener, error) {
l := &Listener{}
l.addr = tunnelAddr{network: "tcp", address: addr}
l.chErr = make(chan error)
l.chIO = make(chan io.ReadWriteCloser)

// Doing a for loop so that it will retry even the tunnel server is not reachable.
bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 0 // Retry Subscribe indefinitely.
bo.InitialInterval = RetryBaseDelay
bo.MaxInterval = RetryMaxDelay
bo.RandomizationFactor = RetryRandomization

for {
if c, err := registerTunnelClient(ctx, addr, cert, l, targets); err == nil {
go func() {
c.Start(ctx)
if err := c.Error(); err != nil {
l.chErr <- err
}
}()
return l, nil
}

// tunnel client establishes a tunnel session if it succeeded.
// retry if it fails.
select {
case err := <-l.chErr:
log.Printf("failed to get tunnel listener: %v", err)
default:
}
duration := bo.NextBackOff()
log.Printf("Tunnel listener will retry in %s.", duration)
time.Sleep(duration)
}
}
Loading

0 comments on commit 55444bc

Please # to comment.