Skip to content

Commit 5b19e5e

Browse files
committed
fix: use independent pipes in vpn tunnel tests
1 parent 6116776 commit 5b19e5e

File tree

1 file changed

+54
-3
lines changed

1 file changed

+54
-3
lines changed

vpn/tunnel_internal_test.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package vpn
22

33
import (
44
"context"
5+
"io"
56
"net"
67
"net/netip"
78
"net/url"
@@ -485,9 +486,7 @@ func TestTunnel_sendAgentUpdate(t *testing.T) {
485486

486487
//nolint:revive // t takes precedence
487488
func setupTunnel(t *testing.T, ctx context.Context, client *fakeClient, mClock quartz.Clock) (*Tunnel, *speaker[*ManagerMessage, *TunnelMessage, TunnelMessage]) {
488-
mp, tp := net.Pipe()
489-
t.Cleanup(func() { _ = mp.Close() })
490-
t.Cleanup(func() { _ = tp.Close() })
489+
mp, tp := newBidiPipe(t)
491490
logger := testutil.Logger(t)
492491

493492
var tun *Tunnel
@@ -510,3 +509,55 @@ func setupTunnel(t *testing.T, ctx context.Context, client *fakeClient, mClock q
510509
mgr.start()
511510
return tun, mgr
512511
}
512+
513+
// In practice, the underlying connection is a pair of dup'd pipes. The tunnel
514+
// closing the connection (the dup'd FD) should not cause the manager to read EOF.
515+
// By replicating the behavior of these FDs, we avoid a race where the manager
516+
// reads EOF before it reads the stop response from the tunnel.
517+
type bidiPipe struct {
518+
rw io.ReadWriteCloser
519+
*refcnt
520+
}
521+
522+
type refcnt struct {
523+
count int
524+
mu sync.Mutex
525+
closeFunc func() error
526+
}
527+
528+
// Not idempotent, but it's only called once per bidiPipe.
529+
func (b *bidiPipe) Close() error {
530+
b.refcnt.mu.Lock()
531+
defer b.refcnt.mu.Unlock()
532+
533+
b.refcnt.count--
534+
if b.refcnt.count == 0 {
535+
_ = b.refcnt.closeFunc()
536+
}
537+
return nil
538+
}
539+
540+
func (b *bidiPipe) Read(p []byte) (n int, err error) {
541+
return b.rw.Read(p)
542+
}
543+
544+
func (b *bidiPipe) Write(p []byte) (n int, err error) {
545+
return b.rw.Write(p)
546+
}
547+
548+
func newBidiPipe(t *testing.T) (io.ReadWriteCloser, io.ReadWriteCloser) {
549+
mp, tp := net.Pipe()
550+
t.Cleanup(func() { _ = mp.Close() })
551+
t.Cleanup(func() { _ = tp.Close() })
552+
553+
refcnt := &refcnt{
554+
count: 2,
555+
closeFunc: func() error {
556+
_ = tp.Close()
557+
_ = mp.Close()
558+
return nil
559+
},
560+
}
561+
562+
return &bidiPipe{rw: tp, refcnt: refcnt}, &bidiPipe{rw: mp, refcnt: refcnt}
563+
}

0 commit comments

Comments
 (0)