A joining machine dials the bus once the hub has answered its tunnel
Issuing the token sends the hub its new peer, and the hub applies it on its own time; a bus dialled before then timed out naming the bus. The first tunnel now waits for a handshake with the hub, and says so in the tunnel's words when there is none (novox/hq ADR 0169).
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/novox/mesh-host/internal/apply"
|
"github.com/novox/mesh-host/internal/apply"
|
||||||
"github.com/novox/mesh-host/internal/identity"
|
"github.com/novox/mesh-host/internal/identity"
|
||||||
@@ -115,5 +116,38 @@ func bringTheTunnelUp(ctx context.Context, t *identity.TokenTunnel, keyPath stri
|
|||||||
return fmt.Errorf("the first tunnel would not start (%s): %v %s", tunnelUnit, err, strings.TrimSpace(out))
|
return fmt.Errorf("the first tunnel would not start (%s): %v %s", tunnelUnit, err, strings.TrimSpace(out))
|
||||||
}
|
}
|
||||||
fmt.Printf("the tunnel to the hub is up: %s, through %s\n", t.Address, t.HubEndpoint)
|
fmt.Printf("the tunnel to the hub is up: %s, through %s\n", t.Address, t.HubEndpoint)
|
||||||
return nil
|
return waitForTheHub(ctx, run, handshakeWithin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handshakeWithin is how long the hub has to answer the first tunnel. Issuing the token sent the hub
|
||||||
|
// this machine as a peer; the hub applies that on its own time, and a bus dialled before it has is a
|
||||||
|
// timeout that names the bus rather than the tunnel.
|
||||||
|
var handshakeWithin = 90 * time.Second
|
||||||
|
|
||||||
|
// waitForTheHub waits until the tunnel has shaken hands with the hub, so the bus is dialled over a
|
||||||
|
// tunnel that answers — and says so in the tunnel's own words when it does not.
|
||||||
|
func waitForTheHub(ctx context.Context, run apply.Runner, within time.Duration) error {
|
||||||
|
deadline := time.Now().Add(within)
|
||||||
|
for {
|
||||||
|
out, err := run(ctx, "wg", "show", "mesh0", "latest-handshakes")
|
||||||
|
if err == nil {
|
||||||
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) == 2 && fields[1] != "0" {
|
||||||
|
fmt.Println("the hub answered the tunnel")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Now().After(deadline) {
|
||||||
|
return fmt.Errorf("the hub has not answered the tunnel in %s: the token may be another machine's, "+
|
||||||
|
"the hub may not have been sent this machine as a peer, or its tunnel's port is not reachable "+
|
||||||
|
"from here", within)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/novox/mesh-host/internal/identity"
|
"github.com/novox/mesh-host/internal/identity"
|
||||||
)
|
)
|
||||||
@@ -67,6 +68,9 @@ func TestTheFirstTunnelIsTheMeshsInterfaceWithTheHubAsItsPeer(t *testing.T) {
|
|||||||
t.Cleanup(func() { tunnelConfigPath = "/etc/wireguard/mesh0.conf" })
|
t.Cleanup(func() { tunnelConfigPath = "/etc/wireguard/mesh0.conf" })
|
||||||
var ran []string
|
var ran []string
|
||||||
run := func(_ context.Context, name string, args ...string) (string, error) {
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
||||||
|
if name == "wg" {
|
||||||
|
return "HUBKEY\t1759400000\n", nil
|
||||||
|
}
|
||||||
ran = append(ran, name+" "+strings.Join(args, " "))
|
ran = append(ran, name+" "+strings.Join(args, " "))
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -110,3 +114,26 @@ func captureStdout(t *testing.T, fn func()) string {
|
|||||||
out, _ := io.ReadAll(r)
|
out, _ := io.ReadAll(r)
|
||||||
return string(out)
|
return string(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The bus is dialled only once the hub has answered the tunnel, and a hub that never does is said
|
||||||
|
// as the tunnel's fault rather than the bus's.
|
||||||
|
func TestTheBusWaitsForTheHubToAnswer(t *testing.T) {
|
||||||
|
asked := 0
|
||||||
|
answersOnThird := func(_ context.Context, name string, args ...string) (string, error) {
|
||||||
|
asked++
|
||||||
|
if asked < 3 {
|
||||||
|
return "HUBKEY\t0\n", nil
|
||||||
|
}
|
||||||
|
return "HUBKEY\t1759400000\n", nil
|
||||||
|
}
|
||||||
|
captureStdout(t, func() {
|
||||||
|
if err := waitForTheHub(context.Background(), answersOnThird, time.Minute); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
never := func(context.Context, string, ...string) (string, error) { return "HUBKEY\t0\n", nil }
|
||||||
|
err := waitForTheHub(context.Background(), never, 0)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "has not answered the tunnel") {
|
||||||
|
t.Fatalf("a hub that never answered was not said: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user