mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-02 02:38:51 -04:00
104 lines
3.9 KiB
Go
104 lines
3.9 KiB
Go
package daemonaddr
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
const (
|
|
// WindowsPipeAddr is the default daemon address on Windows. A named pipe
|
|
// carries the connecting process's token, which loopback TCP does not, so
|
|
// it is the only Windows transport on which the daemon can tell who is
|
|
// calling it.
|
|
WindowsPipeAddr = "npipe://netbird"
|
|
|
|
// legacyWindowsAddr is the loopback-TCP address the Windows daemon used
|
|
// before named-pipe support.
|
|
legacyWindowsAddr = "tcp://127.0.0.1:41731"
|
|
|
|
pipeScheme = "npipe://"
|
|
|
|
// protectedPrefix is the NPFS namespace in which only LocalSystem and
|
|
// members of BUILTIN\Administrators may create a pipe. A daemon running as
|
|
// the service account creates its pipe there so that an unprivileged process
|
|
// cannot pre-create the name, which would keep the daemon from starting and
|
|
// leave callers talking to the squatter. Opening such a pipe needs no
|
|
// privilege, so unprivileged clients still reach the daemon.
|
|
protectedPrefix = `ProtectedPrefix\Administrators\`
|
|
)
|
|
|
|
// DialTarget returns the gRPC dial target and transport options for a daemon
|
|
// address. The npipe scheme needs a context dialer because gRPC has no
|
|
// named-pipe resolver; unix and tcp are handled by gRPC itself.
|
|
func DialTarget(addr string) (string, []grpc.DialOption) {
|
|
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
|
|
|
|
if name, ok := strings.CutPrefix(addr, pipeScheme); ok {
|
|
paths := PipePaths(name)
|
|
opts = append(opts, grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
|
return dialPipePaths(ctx, paths)
|
|
}))
|
|
return "passthrough:///netbird-daemon-pipe", opts
|
|
}
|
|
|
|
return strings.TrimPrefix(addr, "tcp://"), opts
|
|
}
|
|
|
|
// PipePath maps an npipe address name ("netbird", from "npipe://netbird") to a
|
|
// Windows named-pipe path (\\.\pipe\netbird). A fully qualified path is left as
|
|
// is.
|
|
func PipePath(name string) string {
|
|
if strings.HasPrefix(name, `\\`) {
|
|
return name
|
|
}
|
|
return `\\.\pipe\` + name
|
|
}
|
|
|
|
// PipePaths returns the paths a daemon control pipe may live at for an npipe
|
|
// address name, in the order both sides must try them: the protected name first,
|
|
// then the plain one.
|
|
//
|
|
// The daemon serves the first it can create, which is the protected name when it
|
|
// runs as the service account and the plain one when it runs as an ordinary user,
|
|
// as it does in netstack mode. Clients therefore have to try both, and because a
|
|
// client cannot tell from the name alone who created the pipe, the plain name is
|
|
// only usable once the server's identity has been checked: see
|
|
// verifyPipeServer.
|
|
//
|
|
// A fully qualified path is what the operator asked for and is used as is.
|
|
func PipePaths(name string) []string {
|
|
if strings.HasPrefix(name, `\\`) {
|
|
return []string{name}
|
|
}
|
|
return []string{PipePath(protectedPrefix + name), PipePath(name)}
|
|
}
|
|
|
|
// IsProtectedPipePath reports whether a pipe path is in the namespace only an
|
|
// administrator or LocalSystem can create in, which is what lets a client trust
|
|
// such a pipe from its name alone.
|
|
func IsProtectedPipePath(path string) bool {
|
|
return strings.HasPrefix(path, `\\.\pipe\`+protectedPrefix)
|
|
}
|
|
|
|
// MigrateLegacy upgrades the pre-named-pipe Windows daemon address to the named
|
|
// pipe, reporting whether it rewrote the address. Existing installs persist the
|
|
// daemon address, so without this an upgraded daemon would keep listening on
|
|
// loopback TCP, where callers carry no identity and privileged operations would
|
|
// have to be refused for everyone. Only the exact legacy default is rewritten:
|
|
// a deliberately chosen custom address is left alone.
|
|
func MigrateLegacy(addr string) (string, bool) {
|
|
return migrateLegacyForOS(runtime.GOOS, addr)
|
|
}
|
|
|
|
func migrateLegacyForOS(goos, addr string) (string, bool) {
|
|
if goos == "windows" && addr == legacyWindowsAddr {
|
|
return WindowsPipeAddr, true
|
|
}
|
|
return addr, false
|
|
}
|