bastion 0.55.3
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package main
|
||||
|
||||
// dialEach, which both egress lanes dial through: an address whose packets go
|
||||
// nowhere must not hold up the next one for its share of the dial budget, the
|
||||
// way the OS dialler's Happy Eyeballs never did when it was given the name.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type closeRecordingConn struct {
|
||||
net.Conn
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (c *closeRecordingConn) Close() error {
|
||||
c.closed.Store(true)
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// MUTATION: dial the addresses one after another, each with an even share of
|
||||
// what is left of the budget.
|
||||
// RESULT: red — the second address waits out the first one's share (2 s here).
|
||||
func TestDialEachDoesNotWaitOutAnAddressThatGoesNowhere(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
|
||||
defer cancel()
|
||||
var mu sync.Mutex
|
||||
var dialled []string
|
||||
abandoned := make(chan struct{})
|
||||
dial := func(ctx context.Context, _, addr string) (net.Conn, error) {
|
||||
mu.Lock()
|
||||
dialled = append(dialled, addr)
|
||||
mu.Unlock()
|
||||
if addr == "[2001:db8::30]:443" {
|
||||
<-ctx.Done() // no answer and no error: the packets go nowhere
|
||||
close(abandoned)
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
a, b := net.Pipe()
|
||||
go func() { _ = b.Close() }()
|
||||
return a, nil
|
||||
}
|
||||
began := time.Now()
|
||||
c, err := dialEach(ctx, dial, []string{"[2001:db8::30]:443", "198.51.100.30:443"})
|
||||
took := time.Since(began)
|
||||
if err != nil {
|
||||
t.Fatalf("the second address answers: %v", err)
|
||||
}
|
||||
_ = c.Close()
|
||||
if took > 1500*time.Millisecond {
|
||||
t.Fatalf("the second address waited %v for the first", took)
|
||||
}
|
||||
select {
|
||||
case <-abandoned:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("the attempt left in flight must be cancelled once another answers")
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if want := []string{"[2001:db8::30]:443", "198.51.100.30:443"}; !reflect.DeepEqual(dialled, want) {
|
||||
t.Fatalf("only the vetted addresses, in order: got %v, want %v", dialled, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A connection that answers after another already won is closed, not leaked;
|
||||
// an address that fails at once hands over to the next without waiting; and
|
||||
// when every address fails the last error is returned.
|
||||
func TestDialEachClosesALateWinnerAndReturnsTheLastError(t *testing.T) {
|
||||
late := make(chan *closeRecordingConn, 1)
|
||||
dial := func(ctx context.Context, _, addr string) (net.Conn, error) {
|
||||
switch addr {
|
||||
case "198.51.100.31:443":
|
||||
time.Sleep(2 * dialEachStagger) // answers, but after the second address did
|
||||
a, b := net.Pipe()
|
||||
go func() { _ = b.Close() }()
|
||||
rc := &closeRecordingConn{Conn: a}
|
||||
late <- rc
|
||||
return rc, nil
|
||||
case "198.51.100.32:443":
|
||||
a, b := net.Pipe()
|
||||
go func() { _ = b.Close() }()
|
||||
return a, nil
|
||||
}
|
||||
return nil, errors.New("refused: " + addr)
|
||||
}
|
||||
c, err := dialEach(context.Background(), dial, []string{"198.51.100.31:443", "198.51.100.32:443"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = c.Close()
|
||||
select {
|
||||
case rc := <-late:
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for !rc.closed.Load() && time.Now().Before(deadline) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if !rc.closed.Load() {
|
||||
t.Fatal("a connection that answered after the winner must be closed")
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("the slow attempt never answered")
|
||||
}
|
||||
|
||||
began := time.Now()
|
||||
_, err = dialEach(context.Background(), dial, []string{"198.51.100.33:443", "198.51.100.34:443"})
|
||||
if err == nil || err.Error() != "refused: 198.51.100.34:443" {
|
||||
t.Fatalf("every address failed, so the last error: %v", err)
|
||||
}
|
||||
if took := time.Since(began); took >= dialEachStagger {
|
||||
t.Fatalf("a failed address hands over at once, not after the stagger: %v", took)
|
||||
}
|
||||
if _, err := dialEach(context.Background(), dial, nil); err == nil {
|
||||
t.Fatal("no address is an error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user