grpctunnel

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2023 License: Apache-2.0 Imports: 21 Imported by: 3

README

gRPC Tunnels

Build Status Go Report Card GoDoc

This library enables carrying gRPC over gRPC. There are a few niche use cases where this could be useful, but the most widely applicable one is likely for letting gRPC servers communicate in the reverse direction, sending requests to connected clients.

The tunnel is itself a gRPC service, which provides bidirectional streaming methods for forward and reverse tunneling.

  • Forward Tunnel: A forward tunnel is the same direction as normal gRPC connections. This means that the gRPC client that created the tunnel is also the client for requests that flow through the tunnel. For forward cases, the server must register service handlers for the gRPC services that can be accessed over the tunnel.

    A forward tunnel allows for all requests made on the tunnel to be directed to the same server. With a typical gRPC client, connecting to a replicated server, requests are typically load balanced across backends. For typical stateless applications, this is desirable for resource utilization and fault tolerance. But some applications that are not stateless may need affinity. The tunnel provides that affinity. Instead of the client making multiple requests, which could all be directed to different backends, the client makes one request to open a tunnel. The resulting tunnel can then be used to create other RPC stubs, so that all requests issued via those stubs are directed to the single backend to which the tunnel was opened.

  • Reverse Tunnel: A reverse tunnel is the opposite: requests flow in the reverse direction of a normal gRPC connection. This means that the gRPC client that created the tunnel actually acts as the server. The gRPC server with which the reverse tunnel was opened acts as the client, sending requests to the gRPC client.

    A reverse tunnel allows for rich "server push"-like capabilities, where a server can push data to the client (by initiating an RPC through the reverse tunnel) and even get back a response. This kind of functionality can be built using regular gRPC bidirectional streams, but this library provides a better and more familiar abstraction. A reverse tunnel is also classically used in cases where the server cannot be directly dialed due to network topology (e.g. being behind NAT). In these cases, the server dials a central router and registers itself, allowing the router to forward requests to the server over the reverse tunnel.

Terminology

Talk of tunneling can make concepts like "client" and "server" confusing, since reverse tunnels swap the typical roles. So in the hopes of clarity, the rest of this document will use the following terms to hopefully avoid confusion:

  • channel: A conduit through which gRPC requests can be sent and responses received. This can be a TCP connection but can also be a tunnel.
  • tunnel: A single gRPC stream that can act as a channel, carrying other gRPC requests and responses over that stream.
  • tunnel service: The gRPC service via which a tunnel is opened. This is a service named grpctunnel.v1.TunnelService and defines the actual tunneling protocol, in the form of bidirectional streaming RPCs.
  • tunnel handler: The implementation of the tunnel service. This is the code that handles RPCs to the grpctunnel.v1.TunnelService and actually implements tunnels.
  • network client: The gRPC client that opened the tunnel. For typical TCP connections, this is the TCP client.
  • network server: The gRPC server to which a tunnel was opened. For typical TCP connections, this is the TCP server.
  • tunnel client: The gRPC client that initiates requests on a tunnel.
  • tunnel server: The gRPC server handles requests on a tunnel.
  • forward tunnel: A tunnel in which the network client is also the tunnel client, and the network server is also the tunnel server.
  • reverse tunnel: A tunnel in which the network server is actually the tunnel client, and the network client acts as the tunnel server.

Tunnel Handler

The tunnel handler is constructed via grpctunnel.NewTunnelServiceHandler. The options provided with this call are for configuring reverse tunnel support. The handler has other methods that can be used to interact with reverse tunnels that have been established.

The resulting handler has a Service method, which returns the actual service implementation, which can then be registered with a gRPC server using tunnelpb.RegisterTunnelServiceServer.

handler := grpctunnel.NewTunnelServiceHandler(
    grpctunnel.TunnelServiceHandlerOptions{},
)
svr := grpc.NewServer()
tunnelpb.RegisterTunnelServiceServer(svr, handler.Service())

// TODO: Configure services for forward tunnels.
// TODO: Inject handler into code that will use reverse tunnels.

// Start the gRPC server.
l, err := net.Listen("tcp", "0.0.0.0:7899")
if err != nil {
    log.Fatal(err)
}
if err := svr.Serve(l); err != nil {
    log.Fatal(err)
}

Forward Tunnels

A forward tunnel is one in which the tunnel client is the same as the network client, and the tunnel server is the same as the network server. So the client that opened the tunnel is also the one that initiates RPCs through the tunnel.

sequenceDiagram
    participant network client
    participant network server
    network client->>+network server: opens the tunnel via RPC `grpctunnel.v1.TunnelService/OpenTunnel`
    rect rgb(245, 248, 255)
      loop forward tunnel created
        network client->>+network server: sends RPC request over the tunnel
	network server->>network server: handles request
        network server->>-network client: sends RPC response over the tunnel
      end
    end
    network server->>-network client: tunnel closed
Client

The tunnel is opened by the client using the generated stub for the tunneling protocol: tunnelpb.TunnelServiceClient.

Once a stream has been established via the stub's OpenTunnel method, it can be used to create a channel via grpctunnel.NewChannel.

That channel can be used to create other stubs, as if it were a *grpc.ClientConn. All RPCs issued from stubs created with this channel will be directed through the tunnel.

// Dial the server.
cc, err := grpc.Dial(
	"127.0.0.1:7899",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
	log.Fatal(err)
}

tunnelStub := tunnelpb.NewTunnelServiceClient(cc)
// Opens a tunnel and return a channel.
ch, err := grpctunnel.NewChannel(tunnelStub).Start(context.Background())
if err != nil {
	log.Fatal(err)
}

// TODO: Create stubs using ch to send RPCs through the tunnel.

To close the tunnel, use the channel's Close method. This will also close the underlying stream. If any RPCs are in progress on the channel when it is closed, they will be cancelled. The channel is also closed if the context passed to Start is cancelled or times out.

To use client interceptors with these channels, wrap them using grpchan.InterceptClientConn before creating stubs.

Server

To handle RPCs that are issued over tunnels, the server must register service handlers using the TunnelServiceHandler.

handler := grpctunnel.NewTunnelServiceHandler(
    grpctunnel.TunnelServiceHandlerOptions{},
)
svr := grpc.NewServer()
tunnelpb.RegisterTunnelServiceServer(svr, handler.Service())

// Register services to be used with forward tunnels.
foopb.RegisterFooServer(handler, &fooServer{})

// To expose a service over both tunnels and non-tunnel connections, you must
// register it with the gRPC server, too.
serviceImpl := newBarServer()
barpb.RegisterBarServer(handler, serviceImpl)
barpb.RegisterBarServer(svr, serviceImpl)

To use server interceptors with these handlers, wrap the TunnelServiceHandler using grpchan.WithInterceptor before registering the other handlers.

Authn/Authz

With forward tunnels, authentication can be done on the initial OpenTunnel RPC that opens the tunnel. The identity of the client can then be stored in a context value, from a server interceptor. These context values are also available to server interceptors and handlers that process tunneled requests. So an authorization interceptor could extract the client identity from the request context.

If using mutual TLS, you can use peer.FromContext (part of the gRPC runtime) to examine the client's identity, which would have been authenticated via client certificate. Like other context values, this value is available to all server interceptors and handlers of tunneled requests and will be the same peer that opened the tunnel.

Reverse Tunnels

A reverse tunnel is one in which the tunnel client is actually the network server; the tunnel server is the network client. So it's the server to which the tunnel was opened that actually initiates RPCs through the tunnel.

sequenceDiagram
    participant network client
    participant network server
    network client->>+network server: opens the tunnel via RPC `grpctunnel.v1.TunnelService/OpenReverseTunnel`
    rect rgb(245, 248, 255)
      loop reverse tunnel created
        network server->>+network client: sends RPC request over the tunnel
        network client->>network client: handles request
        network client->>-network server: sends RPC response over the tunnel
      end
    end
    network server->>-network client: tunnel closed

Because the typical roles of client and server are reversed, usage of reverse tunnels is a bit more complicated than usage of forward tunnels.

Client

The tunnel is opened by the client using the generated stub for the tunneling protocol: tunnelpb.TunnelServiceClient.

However, since the network client will act as the channel server, handlers for the exposed services must be registered before the tunnel is actually created. This is done by creating a grpctunnel.ReverseTunnelServer and then using it to register service implementations, just as one would register service implementations with a *grpc.Server.

Once all services are registered, we can call Serve to actually open the reverse tunnel and accept and process RPC requests sent by the network server.

// Dial the server.
cc, err := grpc.Dial(
	"127.0.0.1:7899",
	grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
	log.Fatal(err)
}

// Register services for reverse tunnels.
tunnelStub := tunnelpb.NewTunnelServiceClient(cc)
channelServer := grpctunnel.NewReverseTunnelServer(tunnelStub)
foopb.RegisterFooServer(channelServer, &fooServer{})
barpb.RegisterBarServer(channelServer, newBarServer())

// Open the reverse tunnel and serve requests.
if _, err := channelServer.Serve(context.Background()); err != nil {
	log.Fatal(err)
}

The Serve function returns once the tunnel is closed, either via the tunnel client closing the channel or some other interruption of the stream (including the context being cancelled or timing out).

To use server interceptors with these handlers, wrap the ReverseTunnelServer using grpchan.WithInterceptor before registering the other handlers.

Server

The network server for reverse services is where things get really interesting. The network server will be acting as a tunnel client. So it needs a way to inspect the reverse tunnels, to decide which one should be used for an RPC. It is even possible to group multiple reverse tunnels so they act like a connection pool, where RPCs can be scattered over multiple tunnels in a round-robin fashion.

When the handler is created, it is given options that control the behavior of reverse tunnels:

  • NoReverseTunnels: Reverse tunnels can be completely disabled this way, which will cause all network clients to receive a "Not Implemented" error if they try to establish reverse tunnels.
  • OnReverseTunnelOpen, OnReverseTunnelClose: These callbacks, if provided, let your application know whenever a reverse tunnel is opened or closed, to track the available tunnels. Each such tunnel is a channel that can be used to send RPCs to the corresponding network client.
  • AffinityKey: This is a function for grouping reverse tunnels. The function is given a TunnelChannel and returns a key. The function has access to the corresponding stream's context, from which it can query for properties that may be useful for grouping -- such as the authenticated peer, request headers provided when the tunnel was opened, and any other context values that may have been populated by interceptors. All reverse tunnels with the same key can be treated like a connection pool using the handler's KeyAsChannel method.

The callbacks provide the most flexibility for how to make use of the available reverse tunnels. But the handler has other methods that should be sufficient for most usages:

  • AsChannel(): Returns a channel that effectively groups all reverse tunnels into a single connection pool. Issuing RPCs with this channel will round-robin through them. This works even if no affinity key function was provided when then handler was created.
  • KeyAsChannel(): Similar to above, but allows for selecting a subset of reverse tunnels to treat as a single pool. This is only useful if an affinity key function is provided when the handler is created.
  • AllReverseTunnels(): Returns a slice of all available reverse tunnels. This allows flexibility for selecting a reverse tunnel, but at a potential performance cost since it requires the caller to re-query and re-scan the slice prior to issuing an RPC. (Storing the slice and using it for future RPCs is risky because the slice is a snapshot that can quickly become stale: new reverse tunnels may be opened and items in the slice may be closed.)

All of these channels can be used just like a *grpc.ClientConn, for creating RPC stubs and then issuing RPCs to the corresponding network client.

To use client interceptors with these channels, wrap them using grpchan.InterceptClientConn before creating stubs.

Authn/Authz

With reverse tunnels, authentication is a little different than with forward tunnels. The credentials associated with the initial OpenReverseTunnel RPC are those for the tunnel server. Unless you are using mutual TLS (where both parties authenticate via certificate), you will need to supply additional authentication material with tunneled requests.

One way to send authentication material is to have the client (which is actually the network server) use client interceptors to include per-call credentials with every request. This approach closely resembles how non-tunneled RPCs are handled: both sides use interceptors to send and verify credentials with every operation.

A more efficient way involves only authenticating once, since all calls over the tunnel will have the same authenticated client. This can be done by having a server interceptor that sends authentication materials in response headers. These will be received by the client almost immediately after the tunnel is opened. This identity can then be stored in the context using a mutable value:

// Client interceptor for the OpenReverseTunnel RPC:
func reverseCredentialsInterceptor(
	ctx context.Context,
	desc *grpc.StreamDesc,
	cc *grpc.ClientConn,
	method string,
	streamer grpc.Streamer,
	opts ...grpc.CallOption,
) (grpc.ClientStream, error) {
	if method != "/grpctunnel.v1.TunnelService/OpenReverseTunnel" {
		return streamer(ctx, desc, cc, method, opts...)
	}
	// Store mutable value in context.
	var authInfo any
	ctx = context.WithValue(ctx, reverseCredentialsKey{}, &authInfo)
	// Invoke RPC; open the tunnel.
	stream, err := streamer(ctx, desc, cc, method, opts...)
	if err != nil {
		return nil, err
	}
	// Get credentials from response headers.
	md, err := stream.Header()
	if err != nil {
		return nil, err
	}
	// If authentication fails, authInfo could include details about
	// the failure so that tunneled RPCs can fail with appropriate
	// error details.
	//
	// An alternative is to just close the tunnel immediately, right
	// here. But then there is no way to send information about the
	// authn error to the peer.
	//
	// Note that modifying authInfo here is okay. But it is not safe
	// to modify after returning from this interceptor since that
	// could lead to data races with tunneled RPCs reading it.
	authInfo = authenticate(md)
	return stream, nil
}

// Server interceptor for tunneled RPCs.
// (Unary interceptor shown; streaming interceptor would be similar.)
func tunneledAuthzInterceptor(
	ctx context.Context,
	method string,
	req, reply interface{},
	cc *grpc.ClientConn,
	invoker grpc.UnaryInvoker,
	opts ...grpc.CallOption,
) error {
	// Get authInfo from context. This was stored in the context by
	// the interceptor above. More detailed error messages could be
	// used or error details added if authInfo contains details about
	// authn failures.
	authInfo, ok := ctx.Value(reverseCredentialsKey{}).(*any)
	if !ok || authInfo == nil || *authInfo == nil {
		return status.Error(codes.Unauthenticated, "unauthenticated")
	}
	if !isAllowed(method, *authInfo) {
		return status.Error(codes.PermissionDenied, "not authorized")
	}
	// RPC is allowed.
	return invoker(ctx, method, req, reply, cc, opts...)
}

Documentation

Overview

Package grpctunnel provides APIs to support tunneling of gRPC services: carrying gRPC calls over a gRPC stream.

A tunnel is a gRPC stream on which you can send other RPC requests, in either direction.

Forward Tunnels

A "forward" tunnel is a tunnel in the normal direction, where the gRPC client initiates RPCs by sending requests, and the gRPC server acts on them and responses.

Forward tunnels allow a client to pin RPCs to a single server since they are all sent over a single stream. Forward tunnels work like so:

  • Client creates a new tunnel by calling NewChannel. This issues an RPC that establishes the forward tunnel. The RPC is a full-duplex bidirectional stream, so can support all manner of streaming RPCs over the tunnel.
  • RPC stubs can then be created using this tunnel. All RPCs issued on it are transmitted over the stream that was established by the RPC mentioned above.
  • Closing the tunnel channel also results in the underlying stream being closed.

Reverse Tunnels

A "reverse" tunnel is a tunnel in the opposite direction of normal: the gRPC server is the actor that initiates RPCs by sending requests, and a gRPC client handles these requests and sends responses.

Reverse tunnels allow for interesting "server push" scenarios, where the pushed messages can be more advanced than "fire and forget" but actually need responses/acknowledgements. Reverse tunnels work like so:

  • Client issues an RPC that establishes the reverse tunnel. The RPC is a full-duplex bidirectional stream, so can support all manner of streaming RPCs over the tunnel. (See NewReverseTunnelServer.)
  • Server registers the reverse tunnel for use as a channel. (See relevant fields in TunnelServiceHandlerOptions.)
  • RPC stubs can be created inside the server, using these reverse tunnel channels. All RPCs issued on this channel are transmitted over the tunnel, on the stream that was established in step 1.
  • Shutting down the reverse tunnel server (which is in the client process that established the tunnels) will close all tunnels and their underlying streams.

Service Handler

The TunnelServiceHandler is what implements the tunneling protocol. You can register the RPC services available for forward tunnels with it. You can also use it to access reverse tunnels, for the server to send RPCs back to the client. See NewTunnelServiceHandler.

This is the value that is registered with a *grpc.Server to expose the Tunnel service.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func TunnelMetadataFromIncomingContext

func TunnelMetadataFromIncomingContext(ctx context.Context) (metadata.MD, bool)

TunnelMetadataFromIncomingContext provides server-side access to the request metadata used to open a tunnel. This can be used from server interceptors or handlers that are handling tunneled requests in a tunnel.

func TunnelMetadataFromOutgoingContext

func TunnelMetadataFromOutgoingContext(ctx context.Context) (metadata.MD, bool)

TunnelMetadataFromOutgoingContext provides client-side access to the request metadata used to open a forward tunnel. This can be used from client interceptors that are processing tunneled requests in a forward tunnel.

For client interceptors in a reverse tunnel, you can use metadata.FromOutgoingContext with the context passed to the interceptor or handler.

func WithTunnelChannel

func WithTunnelChannel(ch *TunnelChannel) grpc.CallOption

WithTunnelChannel provides the caller access to the specific TunnelChanel that was used to send a tunneled RPC. This is similar to using TunnelChannelFromContext except it can be used with unary RPCs, where the invoker never has access to values added to the request context. When the RPC completes, the given location will be updated with the channel that handled the request. If the channel that handled the request was not a tunnel, the location is left unchanged.

The pointer must point to an allocated location. Passing a nil pointer will result in a panic when an RPC is invoked with the returned option.

Types

type PendingChannel added in v0.3.0

type PendingChannel interface {
	Start(ctx context.Context, opts ...grpc.CallOption) (TunnelChannel, error)
}

PendingChannel is an un-started channel. Calling Start will establish the tunnel and returns a value that implements grpc.ClientConnInterface, so it can be used to create stubs and issue other RPCs that are all carried over a single tunnel stream.

The given context defines the lifetime of the stream and therefore of the channel; if the context times out or is cancelled, the channel will be closed.

func NewChannel

func NewChannel(stub tunnelpb.TunnelServiceClient, opts ...TunnelOption) PendingChannel

NewChannel creates a new pending channel that, once started, can be used for issuing RPCs.

type ReverseClientConnInterface

type ReverseClientConnInterface interface {
	grpc.ClientConnInterface
	// Ready returns true if this channel is backed by at least one reverse
	// tunnel. Returns false if there are no active, matching reverse tunnels.
	Ready() bool
	// WaitForReady blocks until either this channel is ready or the given
	// context is cancelled or times out. If the latter, the context error is
	// returned. Otherwise, nil is returned. If the channel is immediately
	// ready, this will not block and will immediately return nil.
	WaitForReady(context.Context) error
}

ReverseClientConnInterface is an RPC channel that reports if there are any active reverse tunnels. If there are no available tunnels, the channel will not be ready. When tunnels are ready, RPCs will be balanced across all such tunnels in a round-robin fashion.

type ReverseTunnelServer

type ReverseTunnelServer struct {
	// contains filtered or unexported fields
}

ReverseTunnelServer is a server that can run on the client side of a gRPC connection, handling requests sent over a reverse tunnel.

Callers must first call NewReverseTunnelServer to create a new instance. Then callers register server handlers with the server and use the Serve method to actually create a reverse tunnel and handle requests.

func NewReverseTunnelServer

func NewReverseTunnelServer(stub tunnelpb.TunnelServiceClient, opts ...TunnelOption) *ReverseTunnelServer

NewReverseTunnelServer creates a new server that uses the given stub to create reverse tunnels.

func (*ReverseTunnelServer) GracefulStop

func (s *ReverseTunnelServer) GracefulStop()

GracefulStop initiates graceful shutdown and waits for the server to stop. During graceful shutdown, no new operations will be allowed to start, but existing operations may proceed. The server stops after all existing operations complete.

To given existing operations a time limit, the caller can also arrange to call Stop after some deadline, which forcibly terminates existing operations.

func (*ReverseTunnelServer) RegisterService

func (s *ReverseTunnelServer) RegisterService(desc *grpc.ServiceDesc, srv interface{})

RegisterService implements grpc.ServiceRegistrar. This allows you to use this server when calling registration functions generated by the Go gRPC plugin for protobuf. For example:

revTunnelSvr := NewReverseTunnelServer(tunnelClient)
foo.RegisterFooServiceServer(revTunnelSvr, myFooServiceImpl{})

All services registered will be available for the other end of the tunnel to invoke.

func (*ReverseTunnelServer) Serve

func (s *ReverseTunnelServer) Serve(ctx context.Context, opts ...grpc.CallOption) (started bool, err error)

Serve creates a new reverse tunnel and handles incoming RPC requests that arrive over that reverse tunnel. Since this is a reverse tunnel, RPC requests are initiated by the server, and this end (the client) processes the requests and sends responses.

The boolean return value indicates whether the tunnel was created or not. If false, the returned error indicates the reason the tunnel could not be created. If true, the tunnel could be created and requests could be serviced. In this case, the returned error indicates the reason the tunnel was stopped. This will be nil if the stream was closed by the other side of the tunnel (the server, acting as an RPC client, hanging up).

Reasons for the tunnel ending abnormally include detection of invalid usage of the stream (RPC client sending references to invalid stream IDs or sending frames for a stream ID in improper order) or if the stream itself fails (for example, if there is a network disruption or the given context is cancelled).

Callers may call this repeatedly, to create multiple, concurrent tunnels to the gRPC server associated with the stub used to create this reverse tunnel server.

func (*ReverseTunnelServer) Stop

func (s *ReverseTunnelServer) Stop()

Stop shuts down the server immediately. On return, the server has returned and any on-going operations have been cancelled.

type TunnelChannel

type TunnelChannel interface {
	grpc.ClientConnInterface

	// Close shuts down the channel, cancelling any outstanding operations and
	// making it unavailable for subsequent operations. For forward tunnels,
	// this also closes the underlying stream.
	//
	// Channels for forward tunnels are implicitly closed if the context used
	// to create the underlying stream is cancelled or times out.
	Close()
	// Context returns the context for this channel. This context is derived
	// from the context associated with the underlying stream.
	//
	// For forward tunnels, this is a client context. So it will include
	// outgoing metadata for the request headers that were used to open the
	// tunnel. For reverse tunnels, this is a server context. So that request
	// metadata will be available as incoming metadata.
	Context() context.Context
	// Done returns a channel that can be used to await the channel closing.
	Done() <-chan struct{}
	// Err returns the error that caused the channel to close. If the channel
	// is not yet closed, this will return nil.
	Err() error
}

TunnelChannel is a special gRPC connection that uses a gRPC stream (a tunnel) as its transport.

See NewChannel for using a forward tunnels. The TunnelServiceHandler provides methods for using reverse tunnels. Both directions use this same interface.

func TunnelChannelFromContext

func TunnelChannelFromContext(ctx context.Context) TunnelChannel

TunnelChannelFromContext returns the TunnelChannel that is handling the given request context. If the given context is not a client-side request context, or if the channel for the request is not a tunnel, this will return nil.

type TunnelOption added in v0.3.0

type TunnelOption interface {
	// contains filtered or unexported methods
}

TunnelOption is an option for configuring the behavior of a tunnel client or tunnel server.

func WithDisableFlowControl added in v0.3.0

func WithDisableFlowControl() TunnelOption

WithDisableFlowControl returns an option that disables the use of flow control, even when the tunnel peer supports it.

NOTE: This should NOT be used in application code. This is intended for test code, to verify that the tunnels work without flow control, to make sure they can interop correctly with older versions of this package, before flow control was introduced.

Eventually, older versions that do not use flow control will not be supported and this option will be removed.

type TunnelServiceHandler

type TunnelServiceHandler struct {
	// contains filtered or unexported fields
}

TunnelServiceHandler provides an implementation for TunnelServiceServer. You can register handlers with it, and it will then expose those handlers for incoming tunnels. If no handlers are registered, the server will reply to OpenTunnel requests with an "Unimplemented" error code. The server may still be used for reverse tunnels

For reverse tunnels, if supported, all connected channels (e.g. all clients that have created reverse tunnels) are available. You can also configure a listener to receive notices when channels are connected and disconnected.

See NewTunnelServiceHandler.

func NewTunnelServiceHandler

func NewTunnelServiceHandler(options TunnelServiceHandlerOptions) *TunnelServiceHandler

NewTunnelServiceHandler creates a new TunnelServiceHandler. The options are used to configure behavior for reverse tunnels. The returned handler is also a grpc.ServiceRegistrar, to register the services that will be available for forward tunnels.

The handler's Service method can be used to actually register the handler with a *grpc.Server (or other grpc.ServiceRegistrar).

func (*TunnelServiceHandler) AllReverseTunnels

func (s *TunnelServiceHandler) AllReverseTunnels() []TunnelChannel

AllReverseTunnels returns the set of all currently active reverse tunnels.

func (*TunnelServiceHandler) AsChannel

AsChannel returns a channel that can be used for issuing RPCs back to clients over reverse tunnels. If no reverse tunnels are established, RPCs will fail with "Unavailable" errors.

The returned channel will use a round-robin strategy to select from available reverse tunnels for any given RPC.

This method panics if the handler was created with an option to disallow the use of reverse tunnels.

func (*TunnelServiceHandler) InitiateShutdown

func (s *TunnelServiceHandler) InitiateShutdown()

InitiateShutdown starts the graceful shutdown process and returns immediately. This should be called when the server wants to shut down. This complements the normal process initiated by calling the GracefulStop method of a *grpc.Server. It prevents new operations from being initiated on any existing tunnel (while the main server's GracefulStop method prevents new tunnels from being established). This allows the server to drain, letting existing operations to complete.

func (*TunnelServiceHandler) KeyAsChannel

func (s *TunnelServiceHandler) KeyAsChannel(key interface{}) ReverseClientConnInterface

KeyAsChannel returns a channel that can be used for issuing RPCs back to clients over reverse tunnels whose affinity key matches the given value. If no reverse tunnels that match are established, RPCs will fail with "Unavailable" errors. If no affinity key function was provided when the handler was created, the only key available will be the nil interface.

The returned channel will use a round-robin strategy to select from matching reverse tunnels for any given RPC.

This method panics if the handler was created with an option to disallow the use of reverse tunnels.

func (*TunnelServiceHandler) RegisterService

func (s *TunnelServiceHandler) RegisterService(desc *grpc.ServiceDesc, srv interface{})

RegisterService implements the grpc.ServiceRegistrar interface. This allows the handler to be passed to generated registration functions, so service implementations can be registered with the handler.

func (*TunnelServiceHandler) Service

Service returns the actual tunnel service implementation to register with a grpc.ServiceRegistrar.

type TunnelServiceHandlerOptions

type TunnelServiceHandlerOptions struct {
	// If set, reverse tunnels will not be allowed. The server will reply to
	// OpenReverseTunnel requests with an "Unimplemented" error code.
	NoReverseTunnels bool
	// If reverse tunnels are allowed, this callback may be configured to
	// receive information when clients open a reverse tunnel.
	OnReverseTunnelOpen func(TunnelChannel)
	// If reverse tunnels are allowed, this callback may be configured to
	// receive information when reverse tunnels are torn down.
	OnReverseTunnelClose func(TunnelChannel)
	// Optional function that accepts a reverse tunnel and returns an affinity
	// key. The affinity key values can be used to look up outbound channels,
	// for targeting calls to particular clients or groups of clients.
	//
	// The given TunnelChannel has a context which can be used to query for the
	// identity of the remote peer. Functions like peer.FromContext will return
	// the peer that opened the reverse tunnel, and metadata.FromIncomingContext
	// will return the request headers used to open the reverse tunnel. If any
	// server interceptors ran when the tunnel was opened, then any values they
	// store in the context is also available.
	AffinityKey func(TunnelChannel) any

	// If true, flow control will be disabled, even when the network client
	// supports flow control.
	DisableFlowControl bool
}

TunnelServiceHandlerOptions contains various fields that can be used to customize a TunnelServiceHandler.

See NewTunnelServiceHandler.

Directories

Path Synopsis
gen
Package tunnelpb contains generated code corresponding to the Protocol Buffer definition of the tunneling protocol.
Package tunnelpb contains generated code corresponding to the Protocol Buffer definition of the tunneling protocol.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL