mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
safesocket: connect to the macOS network extension on darwin (as last resort)
(For cmd/tailscale CLI support on macOS) Signed-off-by: Brad Fitzpatrick <brad@danga.com>
This commit is contained in:
parent
0590ad68be
commit
fee2d9fad4
@ -7,16 +7,34 @@
|
||||
package safesocket
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(apenwarr): handle magic cookie auth
|
||||
func connect(path string, port uint16) (net.Conn, error) {
|
||||
if runtime.GOOS == "darwin" && path == "" && port == 0 {
|
||||
return connectMacOSAppSandbox()
|
||||
}
|
||||
pipe, err := net.Dial("unix", path)
|
||||
if err != nil {
|
||||
if runtime.GOOS == "darwin" {
|
||||
extConn, err := connectMacOSAppSandbox()
|
||||
if err != nil {
|
||||
log.Printf("safesocket: failed to connect to Tailscale IPNExtension: %v", err)
|
||||
} else {
|
||||
return extConn, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return pipe, err
|
||||
@ -48,3 +66,66 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) {
|
||||
os.Chmod(path, 0666)
|
||||
return pipe, 0, err
|
||||
}
|
||||
|
||||
// connectMacOSAppSandbox connects to the Tailscale Network Extension,
|
||||
// which is necessarily running within the macOS App Sandbox. Our
|
||||
// little dance to connect a regular user binary to the sandboxed
|
||||
// nework extension is:
|
||||
//
|
||||
// * the sandboxed IPNExtension picks a random localhost:0 TCP port
|
||||
// to listen on
|
||||
// * it also picks a random hex string that acts as an auth token
|
||||
// * it then creates a file named "sameuserproof-$PORT-$TOKEN" and leaves
|
||||
// that file descriptor open forever.
|
||||
// * then we come along here, running as the same UID, but outside
|
||||
// of the sandbox, and look for it. We can run lsof on our own processes,
|
||||
// but other users on the system can't.
|
||||
// * we parse out the localhost port number and the auth token
|
||||
// * we connect to TCP localhost:$PORT
|
||||
// * we send $TOKEN + "\n"
|
||||
// * server verifies $TOKEN, sends "#IPN\n" if okay.
|
||||
// * server is now protocol switched
|
||||
// * we return the net.Conn and the caller speaks the normal protocol
|
||||
func connectMacOSAppSandbox() (net.Conn, error) {
|
||||
out, err := exec.Command("lsof",
|
||||
"-n", // numeric sockets; don't do DNS lookups, etc
|
||||
"-a", // logical AND remaining options
|
||||
fmt.Sprintf("-u%d", os.Getuid()), // process of same user only
|
||||
"-c", "IPNExtension", // starting with IPNExtension
|
||||
"-F", // machine-readable output
|
||||
).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bs := bufio.NewScanner(bytes.NewReader(out))
|
||||
subStr := []byte(".tailscale.ipn.macos/sameuserproof-")
|
||||
for bs.Scan() {
|
||||
line := bs.Bytes()
|
||||
i := bytes.Index(line, subStr)
|
||||
if i == -1 {
|
||||
continue
|
||||
}
|
||||
f := strings.SplitN(string(line[i+len(subStr):]), "-", 2)
|
||||
if len(f) != 2 {
|
||||
continue
|
||||
}
|
||||
portStr, token := f[0], f[1]
|
||||
c, err := net.Dial("tcp", "localhost:"+portStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error dialing IPNExtension: %w", err)
|
||||
}
|
||||
if _, err := io.WriteString(c, token+"\n"); err != nil {
|
||||
return nil, fmt.Errorf("error writing auth token: %w", err)
|
||||
}
|
||||
buf := make([]byte, 5)
|
||||
const authOK = "#IPN\n"
|
||||
if _, err := io.ReadFull(c, buf); err != nil {
|
||||
return nil, fmt.Errorf("error reading from IPNExtension post-auth: %w", err)
|
||||
}
|
||||
if string(buf) != authOK {
|
||||
return nil, fmt.Errorf("invalid response reading from IPNExtension post-auth")
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find Tailscale's IPNExtension process")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user