libtailscale: add tailscale_loopback_api function

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
David Crawshaw
2023-03-01 20:21:18 -08:00
committed by David Crawshaw
parent 998fdc4759
commit 72c10539e2
4 changed files with 103 additions and 5 deletions

View File

@@ -19,6 +19,7 @@ extern int TsnetSetLogFD(int sd, int fd);
extern int TsnetListen(int sd, char* net, char* addr, int* listenerOut);
extern int TsnetListenerClose(int ld);
extern int TsnetAccept(int ld, int* connOut);
extern int TsnetLoopbackAPI(int sd, char* addrOut, size_t addrLen, char* credOut);
tailscale tailscale_new() {
return TsnetNewServer();
@@ -71,6 +72,10 @@ int tailscale_set_logfd(tailscale sd, int fd) {
return TsnetSetLogFD(sd, fd);
}
int tailscale_loopback_api(tailscale sd, char* addr_out, size_t addrlen, char cred_out[static 33]) {
return TsnetLoopbackAPI(sd, addr_out, addrlen, cred_out);
}
int tailscale_errmsg(tailscale sd, char* buf, size_t buflen) {
return TsnetErrmsg(sd, buf, buflen);
}

View File

@@ -362,3 +362,43 @@ func TsnetSetLogFD(sd C.int, fd int) C.int {
}
return 0
}
//export TsnetLoopbackAPI
func TsnetLoopbackAPI(sd C.int, addrOut *C.char, addrLen C.size_t, credOut *C.char) C.int {
// Panic here to ensure we always leave the out values NUL-terminated.
if addrOut == nil {
panic("loopback_api passed nil addr_out")
} else if addrLen == 0 {
panic("loopback_api passed addrlen of 0")
}
// Start out NUL-termianted to cover error conditions.
*addrOut = '\x00'
*credOut = '\x00'
s, err := getServer(sd)
if err != nil {
return s.recErr(err)
}
addr, cred, err := s.s.LoopbackLocalAPI()
if err != nil {
return s.recErr(err)
}
if len(cred) != 32 {
return s.recErr(fmt.Errorf("libtailscale: len(cred)=%d, want 32", len(cred)))
}
if len(addr)+1 > int(addrLen) {
return s.recErr(fmt.Errorf("libtailscale: loopback addr of %d bytes is too long for addrlen %d", len(addr), addrLen))
}
out := unsafe.Slice((*byte)(unsafe.Pointer(addrOut)), addrLen)
n := copy(out, addr)
out[n] = '\x00'
// credOut is non-nil and 33 bytes long because
// it's defined in C as char cred_out[static 33].
out = unsafe.Slice((*byte)(unsafe.Pointer(credOut)), 33)
copy(out, cred)
out[32] = '\x00'
return 0
}

View File

@@ -118,6 +118,17 @@ extern int tailscale_listener_close(tailscale_listener listener);
// -1 - call tailscale_errmsg for details
extern int tailscale_accept(tailscale_listener listener, tailscale_conn* conn_out);
// tailscale_loopback_api starts a LocalAPI listener on a loopback address.
//
// To make http requests to the LocalAPI:
// 1. cred must be provided as the basic auth password
// 2. the header "Sec-Tailscale: localapi" must be set
//
// The NUL-terminated ip:port address of the LocalAPI is written to addr_out.
// The 32-byte basic auth password + NUL-terminator is written to cred_out.
//
// Returns zero on success or -1 on error, call tailscale_errmsg for details.
extern int tailscale_loopback_api(tailscale sd, char* addr_out, size_t addrlen, char cred_out[static 33]);
// tailscale_errmsg writes the details of the last error to buf.
//

View File

@@ -17,9 +17,15 @@ char* tmps2;
char* control_url = 0;
int addrlen = 128;
char* addr = NULL;
char* cred = NULL;
int errlen = 512;
char* err = NULL;
tailscale s1, s2;
int set_err(tailscale sd, char tag) {
err[0] = tag;
err[1] = ':';
@@ -30,9 +36,11 @@ int set_err(tailscale sd, char tag) {
int test_conn() {
err = calloc(errlen, 1);
addr = calloc(addrlen, 1);
cred = calloc(33, 1);
int ret;
tailscale s1 = tailscale_new();
s1 = tailscale_new();
if ((ret = tailscale_set_control_url(s1, control_url)) != 0) {
return set_err(s1, '0');
}
@@ -46,7 +54,7 @@ int test_conn() {
return set_err(s1, '3');
}
tailscale s2 = tailscale_new();
s2 = tailscale_new();
if ((ret = tailscale_set_control_url(s2, control_url)) != 0) {
return set_err(s2, '4');
}
@@ -102,11 +110,20 @@ int test_conn() {
if ((ret = tailscale_listener_close(ln)) != 0) {
return set_err(s1, 'a');
}
if ((ret = tailscale_close(s1)) != 0) {
if ((ret = tailscale_loopback_api(s1, addr, addrlen, cred)) != 0) {
return set_err(s1, 'b');
}
if ((ret = tailscale_close(s2)) != 0) {
return set_err(s2, 'c');
return 0;
}
int close_conn() {
if (tailscale_close(s1) != 0) {
return set_err(s1, 'd');
}
if (tailscale_close(s2) != 0) {
return set_err(s2, 'e');
}
return 0;
}
@@ -114,6 +131,8 @@ int test_conn() {
import "C"
import (
"flag"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
@@ -161,4 +180,27 @@ func RunTestConn(t *testing.T) {
if C.test_conn() != 0 {
t.Fatal(C.GoString(C.err))
}
req, err := http.NewRequest("GET", "http://"+C.GoString(C.addr)+"/localapi/v0/status", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Sec-Tailscale", "localapi")
req.SetBasicAuth("", C.GoString(C.cred))
res, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
b, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
t.Fatal(err)
}
if res.StatusCode != 200 {
t.Errorf("/status: %d: %s", res.StatusCode, b)
}
if C.close_conn() != 0 {
t.Fatal(C.GoString(C.err))
}
}