Skip to content

Commit 2db0021

Browse files
committed
TUN-8419: Add capnp safe transport
To help support temporary errors that can occur in the capnp rpc calls, a wrapper is introduced to inspect the error conditions and allow for retrying within a short window.
1 parent eb2e434 commit 2db0021

File tree

6 files changed

+79
-5
lines changed

6 files changed

+79
-5
lines changed

tunnelrpc/proto/tunnelrpc.capnp.go

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tunnelrpc/quic/cloudflared_client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/google/uuid"
1313

14+
"github.com/cloudflare/cloudflared/tunnelrpc"
1415
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
1516
)
1617

@@ -29,7 +30,7 @@ func NewCloudflaredClient(ctx context.Context, stream io.ReadWriteCloser, reques
2930
if n != len(rpcStreamProtocolSignature) {
3031
return nil, fmt.Errorf("expect to write %d bytes for RPC stream protocol signature, wrote %d", len(rpcStreamProtocolSignature), n)
3132
}
32-
transport := rpc.StreamTransport(stream)
33+
transport := tunnelrpc.SafeTransport(stream)
3334
conn := rpc.NewConn(transport)
3435
client := pogs.NewCloudflaredServer_PogsClient(conn.Bootstrap(ctx), conn)
3536
return &CloudflaredClient{

tunnelrpc/quic/cloudflared_server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"zombiezen.com/go/capnproto2/rpc"
1010

11+
"github.com/cloudflare/cloudflared/tunnelrpc"
1112
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
1213
)
1314

@@ -53,7 +54,7 @@ func (s *CloudflaredServer) Serve(ctx context.Context, stream io.ReadWriteCloser
5354
func (s *CloudflaredServer) handleRPC(ctx context.Context, stream io.ReadWriteCloser) error {
5455
ctx, cancel := context.WithTimeout(ctx, s.responseTimeout)
5556
defer cancel()
56-
transport := rpc.StreamTransport(stream)
57+
transport := tunnelrpc.SafeTransport(stream)
5758
defer transport.Close()
5859

5960
main := pogs.CloudflaredServer_ServerToClient(s.sessionManager, s.configManager)

tunnelrpc/quic/session_client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/google/uuid"
1111
"zombiezen.com/go/capnproto2/rpc"
1212

13+
"github.com/cloudflare/cloudflared/tunnelrpc"
1314
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
1415
)
1516

@@ -28,7 +29,7 @@ func NewSessionClient(ctx context.Context, stream io.ReadWriteCloser, requestTim
2829
if n != len(rpcStreamProtocolSignature) {
2930
return nil, fmt.Errorf("expect to write %d bytes for RPC stream protocol signature, wrote %d", len(rpcStreamProtocolSignature), n)
3031
}
31-
transport := rpc.StreamTransport(stream)
32+
transport := tunnelrpc.SafeTransport(stream)
3233
conn := rpc.NewConn(transport)
3334
return &SessionClient{
3435
client: pogs.NewSessionManager_PogsClient(conn.Bootstrap(ctx), conn),

tunnelrpc/quic/session_server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"zombiezen.com/go/capnproto2/rpc"
1010

11+
"github.com/cloudflare/cloudflared/tunnelrpc"
1112
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
1213
)
1314

@@ -43,7 +44,7 @@ func (s *SessionManagerServer) Serve(ctx context.Context, stream io.ReadWriteClo
4344
ctx, cancel := context.WithTimeout(ctx, s.responseTimeout)
4445
defer cancel()
4546

46-
transport := rpc.StreamTransport(stream)
47+
transport := tunnelrpc.SafeTransport(stream)
4748
defer transport.Close()
4849

4950
main := pogs.SessionManager_ServerToClient(s.sessionManager)

tunnelrpc/utils.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tunnelrpc
2+
3+
import (
4+
"io"
5+
"time"
6+
7+
"github.com/pkg/errors"
8+
"zombiezen.com/go/capnproto2/rpc"
9+
)
10+
11+
const (
12+
// These default values are here so that we give some time for the underlying connection/stream
13+
// to recover in the face of what we believe to be temporarily errors.
14+
// We don't want to be too aggressive, as the end result of giving a final error (non-temporary)
15+
// will result in the connection to be dropped.
16+
// In turn, the other side will probably reconnect, which will put again more pressure in the overall system.
17+
// So, the best solution is to give it some conservative time to recover.
18+
defaultSleepBetweenTemporaryError = 500 * time.Millisecond
19+
defaultMaxRetries = 3
20+
)
21+
22+
type readWriterSafeTemporaryErrorCloser struct {
23+
io.ReadWriteCloser
24+
25+
retries int
26+
sleepBetweenRetries time.Duration
27+
maxRetries int
28+
}
29+
30+
func (r *readWriterSafeTemporaryErrorCloser) Read(p []byte) (n int, err error) {
31+
n, err = r.ReadWriteCloser.Read(p)
32+
33+
// if there was a failure reading from the read closer, and the error is temporary, try again in some seconds
34+
// otherwise, just fail without a temporary error.
35+
if n == 0 && err != nil && isTemporaryError(err) {
36+
if r.retries >= r.maxRetries {
37+
return 0, errors.Wrap(err, "failed read from capnproto ReaderWriter after multiple temporary errors")
38+
} else {
39+
r.retries += 1
40+
41+
// sleep for some time to prevent quick read loops that cause exhaustion of CPU resources
42+
time.Sleep(r.sleepBetweenRetries)
43+
}
44+
}
45+
46+
if err == nil {
47+
r.retries = 0
48+
}
49+
50+
return n, err
51+
}
52+
53+
func SafeTransport(rw io.ReadWriteCloser) rpc.Transport {
54+
return rpc.StreamTransport(&readWriterSafeTemporaryErrorCloser{
55+
ReadWriteCloser: rw,
56+
maxRetries: defaultMaxRetries,
57+
sleepBetweenRetries: defaultSleepBetweenTemporaryError,
58+
})
59+
}
60+
61+
// isTemporaryError reports whether e has a Temporary() method that
62+
// returns true.
63+
func isTemporaryError(e error) bool {
64+
type temp interface {
65+
Temporary() bool
66+
}
67+
t, ok := e.(temp)
68+
return ok && t.Temporary()
69+
}

0 commit comments

Comments
 (0)