libtailscale: make tailscale_listener pollable

Use a socketpair(2) and sendmsg/recvmsg to pass a connection fd
from Go to C. This lets people write non-blocking C by polling on a
tailscale_listener for when they should tailscale_accept.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
David Crawshaw
2023-03-12 14:09:31 -07:00
committed by David Crawshaw
parent 42597d5fb7
commit b0e2f4a4e4
8 changed files with 170 additions and 110 deletions
+39 -16
View File
@@ -2,6 +2,7 @@ package main
import (
"testing"
"time"
"github.com/tailscale/libtailscale/tsnetctest"
)
@@ -11,27 +12,49 @@ func TestConn(t *testing.T) {
// RunTestConn cleans up after itself, so there shouldn't be
// anything left in the global maps.
conns.mu.Lock()
rem := len(conns.m)
conns.mu.Unlock()
if rem > 0 {
t.Fatalf("want no remaining tsnet_conn objects, got %d", rem)
}
listeners.mu.Lock()
rem = len(listeners.m)
listeners.mu.Unlock()
if rem > 0 {
t.Fatalf("want no remaining tsnet_listener objects, got %d", rem)
}
servers.mu.Lock()
rem = len(servers.m)
rem := len(servers.m)
servers.mu.Unlock()
if rem > 0 {
t.Fatalf("want no remaining tsnet objects, got %d", rem)
}
var remConns, remLns int
for i := 0; i < 50; i++ {
conns.mu.Lock()
remConns = len(conns.m)
conns.mu.Unlock()
listeners.mu.Lock()
remLns = len(listeners.m)
listeners.mu.Unlock()
if remConns == 0 && remLns == 0 {
break
}
// We are waiting for cleanup goroutines to finish.
//
// libtailscale closes one side of a socketpair and
// then Go responds to the other side being unreadable
// by closing the connections and listeners.
//
// This is inherently asynchronous.
// Without ditching the standard close(2) and having our
// own close functions.
//
// So we spin for a while
time.Sleep(100 * time.Millisecond)
}
if remConns > 0 {
t.Errorf("want no remaining tsnet_conn objects, got %d", remConns)
}
if remLns > 0 {
t.Errorf("want no remaining tsnet_listener objects, got %d", remLns)
}
}