mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-09 20:01:54 -04:00
Linux is a weak-host stack, so a LAN-adjacent machine can complete a TCP handshake with a node's peerapi listener by sending a packet to the node's Tailscale IP, with no credentials and no tailnet membership. macOS and iOS already bind the listener to the tunnel interface, and Windows is protected by its strong host model, so Linux tun mode was the only platform that leaked. Bind the Linux listener to the tunnel interface as well, so the kernel only answers connections that arrive from the tunnel or from the local host. A natlab VM test verifies that a same-LAN machine can no longer complete the handshake, while local and peer peerapi keep working. FreeBSD has the same weak-host exposure but no per-socket equivalent, so handling it there with pf is a TODO (#21419). Updates tailscale/corp#48248 Reported-By: Samuel Keeley (@keeleysam) Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I5f8501b0938c9f7aa39c4c12ebddd988c72e89bf
61 lines
1.4 KiB
Go
61 lines
1.4 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package netns
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func TestSocketMarkWorks(t *testing.T) {
|
|
_ = socketMarkWorks()
|
|
// we cannot actually assert whether the test runner has SO_MARK available
|
|
// or not, as we don't know. We're just checking that it doesn't panic.
|
|
}
|
|
|
|
func TestSetListenConfigInterfaceName(t *testing.T) {
|
|
// Setting SO_BINDTODEVICE requires CAP_NET_RAW, which the test
|
|
// runner has when running as root and may or may not have otherwise.
|
|
if os.Geteuid() != 0 {
|
|
t.Skip("skipping; setting SO_BINDTODEVICE requires CAP_NET_RAW")
|
|
}
|
|
|
|
var lc net.ListenConfig
|
|
if err := SetListenConfigInterfaceName(&lc, "lo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ln, err := lc.Listen(context.Background(), "tcp4", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
tcpLn, ok := ln.(*net.TCPListener)
|
|
if !ok {
|
|
t.Fatalf("got listener of type %T, want *net.TCPListener", ln)
|
|
}
|
|
rc, err := tcpLn.SyscallConn()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got string
|
|
var sockErr error
|
|
err = rc.Control(func(fd uintptr) {
|
|
got, sockErr = unix.GetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("RawConn.Control: %v", err)
|
|
}
|
|
if sockErr != nil {
|
|
t.Fatalf("getsockopt SO_BINDTODEVICE: %v", sockErr)
|
|
}
|
|
if got != "lo" {
|
|
t.Errorf("SO_BINDTODEVICE = %q, want %q", got, "lo")
|
|
}
|
|
}
|