mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-21 08:09:55 +00:00
Collapse X11 DISPLAY/XAUTHORITY auto-detect logs into one line
This commit is contained in:
@@ -48,7 +48,7 @@ var (
|
||||
procWTSFreeMemory = wtsapi32.NewProc("WTSFreeMemory")
|
||||
procWTSQuerySessionInformation = wtsapi32.NewProc("WTSQuerySessionInformationW")
|
||||
|
||||
iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
iphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
procGetExtendedTcpTable = iphlpapi.NewProc("GetExtendedTcpTable")
|
||||
)
|
||||
|
||||
@@ -283,7 +283,6 @@ func getSystemTokenForSession(sessionID uint32) (windows.Token, error) {
|
||||
return dup, nil
|
||||
}
|
||||
|
||||
|
||||
// injectEnvVar appends a KEY=VALUE entry to a Unicode environment block.
|
||||
// The block is a sequence of null-terminated UTF-16 strings, terminated by
|
||||
// an extra null. Returns the new []uint16 backing slice; the caller must
|
||||
|
||||
@@ -22,24 +22,24 @@ import (
|
||||
var darwinCaptureOnce sync.Once
|
||||
|
||||
var (
|
||||
cgMainDisplayID func() uint32
|
||||
cgDisplayPixelsWide func(uint32) uintptr
|
||||
cgDisplayPixelsHigh func(uint32) uintptr
|
||||
cgDisplayCreateImage func(uint32) uintptr
|
||||
cgImageGetWidth func(uintptr) uintptr
|
||||
cgImageGetHeight func(uintptr) uintptr
|
||||
cgImageGetBytesPerRow func(uintptr) uintptr
|
||||
cgImageGetBitsPerPixel func(uintptr) uintptr
|
||||
cgImageGetDataProvider func(uintptr) uintptr
|
||||
cgDataProviderCopyData func(uintptr) uintptr
|
||||
cgImageRelease func(uintptr)
|
||||
cfDataGetLength func(uintptr) int64
|
||||
cfDataGetBytePtr func(uintptr) uintptr
|
||||
cfRelease func(uintptr)
|
||||
cgMainDisplayID func() uint32
|
||||
cgDisplayPixelsWide func(uint32) uintptr
|
||||
cgDisplayPixelsHigh func(uint32) uintptr
|
||||
cgDisplayCreateImage func(uint32) uintptr
|
||||
cgImageGetWidth func(uintptr) uintptr
|
||||
cgImageGetHeight func(uintptr) uintptr
|
||||
cgImageGetBytesPerRow func(uintptr) uintptr
|
||||
cgImageGetBitsPerPixel func(uintptr) uintptr
|
||||
cgImageGetDataProvider func(uintptr) uintptr
|
||||
cgDataProviderCopyData func(uintptr) uintptr
|
||||
cgImageRelease func(uintptr)
|
||||
cfDataGetLength func(uintptr) int64
|
||||
cfDataGetBytePtr func(uintptr) uintptr
|
||||
cfRelease func(uintptr)
|
||||
cgRequestScreenCaptureAccess func() bool
|
||||
cgEventCreate func(uintptr) uintptr
|
||||
cgEventGetLocation func(uintptr) cgPoint
|
||||
darwinCaptureReady bool
|
||||
cgEventCreate func(uintptr) uintptr
|
||||
cgEventGetLocation func(uintptr) cgPoint
|
||||
darwinCaptureReady bool
|
||||
)
|
||||
|
||||
// cgPoint mirrors CoreGraphics CGPoint: two doubles, 16 bytes, returned
|
||||
@@ -98,7 +98,6 @@ func initDarwinCapture() {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// CGCapturer captures the macOS main display using Core Graphics.
|
||||
type CGCapturer struct {
|
||||
displayID uint32
|
||||
|
||||
@@ -227,4 +227,3 @@ func swizzleFB32(dst []byte, dstStride int, src []byte, srcStride, w, h int, shi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,6 @@ func (p *FBPoller) ensureCapturerLocked() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var _ ScreenCapturer = (*FBPoller)(nil)
|
||||
var _ captureIntoer = (*FBPoller)(nil)
|
||||
|
||||
|
||||
@@ -18,6 +18,12 @@ import (
|
||||
"github.com/jezek/xgb/xproto"
|
||||
)
|
||||
|
||||
// x11SocketDir is the well-known directory where X servers create their
|
||||
// abstract UNIX-domain sockets, named "X<display>". Used both for
|
||||
// auto-detecting an existing display and for placing/probing sockets of
|
||||
// virtual sessions we spawn.
|
||||
const x11SocketDir = "/tmp/.X11-unix"
|
||||
|
||||
// X11Capturer captures the screen from an X11 display using the MIT-SHM extension.
|
||||
type X11Capturer struct {
|
||||
mu sync.Mutex
|
||||
@@ -26,7 +32,7 @@ type X11Capturer struct {
|
||||
w, h int
|
||||
shmID int
|
||||
shmAddr []byte
|
||||
shmSeg uint32 // shm.Seg
|
||||
shmSeg uint32
|
||||
useSHM bool
|
||||
// bufs double-buffers output images so the X11Poller's capture loop can
|
||||
// overwrite one while the session is still encoding the other. Before
|
||||
@@ -83,7 +89,7 @@ func detectX11FromProc() bool {
|
||||
// detectX11FromSockets checks /tmp/.X11-unix/ for X sockets and uses ps
|
||||
// to find the auth file. Works on FreeBSD and other systems without /proc.
|
||||
func detectX11FromSockets() bool {
|
||||
entries, err := os.ReadDir("/tmp/.X11-unix")
|
||||
entries, err := os.ReadDir(x11SocketDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -96,12 +102,12 @@ func detectX11FromSockets() bool {
|
||||
}
|
||||
display := ":" + name[1:]
|
||||
os.Setenv("DISPLAY", display)
|
||||
log.Infof("auto-detected DISPLAY=%s (from socket)", display)
|
||||
|
||||
// Try to find -auth from ps output.
|
||||
if auth := findXorgAuthFromPS(); auth != "" {
|
||||
auth := findXorgAuthFromPS()
|
||||
if auth != "" {
|
||||
os.Setenv("XAUTHORITY", auth)
|
||||
log.Infof("auto-detected XAUTHORITY=%s (from ps)", auth)
|
||||
log.Infof("auto-detected DISPLAY=%s (from socket) XAUTHORITY=%s (from ps)", display, auth)
|
||||
} else {
|
||||
log.Infof("auto-detected DISPLAY=%s (from socket)", display)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -150,11 +156,12 @@ func parseXorgArgs(args []string) (display, auth string) {
|
||||
|
||||
func setDisplayEnv(display, auth string) {
|
||||
os.Setenv("DISPLAY", display)
|
||||
log.Infof("auto-detected DISPLAY=%s", display)
|
||||
if auth != "" {
|
||||
os.Setenv("XAUTHORITY", auth)
|
||||
log.Infof("auto-detected XAUTHORITY=%s", auth)
|
||||
log.Infof("auto-detected DISPLAY=%s XAUTHORITY=%s", display, auth)
|
||||
return
|
||||
}
|
||||
log.Infof("auto-detected DISPLAY=%s", display)
|
||||
}
|
||||
|
||||
func splitCmdline(data []byte) []string {
|
||||
|
||||
@@ -37,7 +37,7 @@ type copyRectDetector struct {
|
||||
cols, rows int
|
||||
// tileHash[ty*cols + tx] is the current hash of the tile at (tx, ty)
|
||||
// in the previous frame. Lookup uses this to detect stale prevTiles
|
||||
// entries — incremental updates may leave hash→pos entries pointing
|
||||
// entries: incremental updates may leave hash→pos entries pointing
|
||||
// at a tile whose content has since changed.
|
||||
tileHash []uint64
|
||||
// prevTiles maps a tile hash to a (x, y) origin in the previous frame.
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestCopyRectDetector_DetectsVerticalScroll(t *testing.T) {
|
||||
fillTile(prev, tx*ts, ty*ts, ts, byte(tx*40), byte(ty*60), 0x80)
|
||||
}
|
||||
}
|
||||
// cur: simulate a single-tile-row scroll upward — every tile copied from
|
||||
// cur: simulate a single-tile-row scroll upward, every tile copied from
|
||||
// the row below in prev, top row is new content.
|
||||
for ty := 0; ty < 2; ty++ {
|
||||
for tx := 0; tx < 4; tx++ {
|
||||
|
||||
@@ -42,10 +42,10 @@ type winPoint struct {
|
||||
}
|
||||
|
||||
type winCursorInfo struct {
|
||||
Size uint32
|
||||
Flags uint32
|
||||
Cursor windows.Handle
|
||||
PtPos winPoint
|
||||
Size uint32
|
||||
Flags uint32
|
||||
Cursor windows.Handle
|
||||
PtPos winPoint
|
||||
}
|
||||
|
||||
type winIconInfo struct {
|
||||
|
||||
@@ -104,7 +104,9 @@ var (
|
||||
userActivityID uint32
|
||||
preventSleepID uint32
|
||||
preventSleepHeld bool
|
||||
preventSleepRef int // refcount across concurrent injectors/sessions
|
||||
// preventSleepRef tracks the refcount of held assertions across
|
||||
// concurrent injectors and sessions.
|
||||
preventSleepRef int
|
||||
|
||||
darwinInputReady bool
|
||||
darwinEventSource uintptr
|
||||
@@ -503,10 +505,10 @@ func (m *MacInputInjector) postScrollWheel(src uintptr, buttonMask uint16) {
|
||||
}
|
||||
|
||||
// scrollPixelsPerWheelTick is the pixel delta we post for one VNC wheel
|
||||
// button event. noVNC accumulates the host wheel/trackpad deltaY and
|
||||
// emits one press+release per ~10 px, so a real gesture arrives as many
|
||||
// small events; 20 px per event keeps the resulting macOS scroll fluid
|
||||
// without overshooting on a single notch.
|
||||
// button event. Browser-based RFB clients typically emit one press+release
|
||||
// per ~10 px of host wheel/trackpad motion, so a real gesture arrives as
|
||||
// many small events; ~20 px per event keeps the resulting macOS scroll
|
||||
// fluid without overshooting on a single notch.
|
||||
const scrollPixelsPerWheelTick int32 = 22
|
||||
|
||||
func (m *MacInputInjector) postMouse(src uintptr, eventType int32, x, y float64, button int32) {
|
||||
@@ -543,8 +545,8 @@ func (m *MacInputInjector) postScroll(src uintptr, deltaY int32) {
|
||||
return
|
||||
}
|
||||
// CGEventCreateScrollWheelEvent(source, units, wheelCount, wheel1delta).
|
||||
// Pixel units (0) feel smoother under noVNC's "one event per ~10 px of
|
||||
// host wheel" emission than line units (1) where each event jumps a
|
||||
// Pixel units (0) feel smoother given the small per-event deltas typical
|
||||
// of RFB wheel events than line units (1) where each event jumps a
|
||||
// whole line. Variadic C function, pass via SyscallN.
|
||||
r1, _, _ := purego.SyscallN(cgEventCreateScrollWheelEventAddr,
|
||||
src, 0, 1, uintptr(uint32(deltaY)))
|
||||
@@ -568,10 +570,10 @@ func (m *MacInputInjector) SetClipboard(text string) {
|
||||
}
|
||||
|
||||
// TypeText synthesizes the given text as keystrokes via Core Graphics.
|
||||
// Used by the dashboard's Paste button so the host clipboard reaches
|
||||
// the focused remote app even when the app doesn't honor pbpaste-style
|
||||
// clipboard sync (e.g. login screens, locked-down apps). ASCII printable
|
||||
// runes only; others are skipped.
|
||||
// Lets a client push host clipboard content to the focused remote app
|
||||
// even when the app doesn't honor pbpaste-style clipboard sync (e.g.
|
||||
// login screens, locked-down apps). ASCII printable runes only; others
|
||||
// are skipped.
|
||||
func (m *MacInputInjector) TypeText(text string) {
|
||||
wakeDisplay()
|
||||
src := ensureEventSource()
|
||||
|
||||
@@ -48,7 +48,6 @@ const (
|
||||
keyeventfScanCode = 0x0008
|
||||
)
|
||||
|
||||
|
||||
// maxTypedClipboardChars caps the number of characters we will synthesize as
|
||||
// keystrokes when falling back on the Winlogon desktop. Passwords are short;
|
||||
// a huge clipboard getting typed into the login screen would be surprising.
|
||||
|
||||
@@ -219,16 +219,15 @@ func (x *X11InputInjector) SetClipboard(text string) {
|
||||
}
|
||||
}
|
||||
|
||||
// TypeText synthesizes the given text as keystrokes via XTest. We can
|
||||
// no longer just stuff the host clipboard with xclip and expect Ctrl+V
|
||||
// to do the rest, because the Paste button is also used at places where
|
||||
// the focused application isn't a clipboard-aware one (e.g. a TTY login
|
||||
// in an X11 session, an SDDM/GDM password field that ignores XSelection,
|
||||
// or a kiosk app). Typing keystrokes covers all of those.
|
||||
// TypeText synthesizes the given text as keystrokes via XTest. Used in
|
||||
// places where the focused application isn't clipboard-aware (e.g. a TTY
|
||||
// login in an X11 session, an SDDM/GDM password field that ignores
|
||||
// XSelection, or a kiosk app), so stuffing the X clipboard and relying on
|
||||
// Ctrl+V would not reach the input.
|
||||
//
|
||||
// Limitation: only ASCII printable characters are typed. Non-ASCII runes
|
||||
// are skipped: a paste workflow for them needs Wayland-aware text input
|
||||
// or layout introspection that we don't have.
|
||||
// or layout introspection that this path does not implement.
|
||||
func (x *X11InputInjector) TypeText(text string) {
|
||||
const maxChars = 4096
|
||||
count := 0
|
||||
@@ -289,7 +288,7 @@ func (x *X11InputInjector) GetClipboard() string {
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Exit status 1 just means there is no STRING selection set yet,
|
||||
// which is the steady state on a fresh Xvfb session — logging it
|
||||
// which is the steady state on a fresh Xvfb session, logging it
|
||||
// every clipboard poll (2s) floods the trace stream.
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ type SessionTick struct {
|
||||
}
|
||||
|
||||
// sessionTickInterval is how often metricsConn emits a SessionTick. One
|
||||
// second matches noVNC's request cadence so each tick covers roughly one
|
||||
// FBU round-trip during steady-state activity.
|
||||
// second covers roughly one FBU round-trip at typical client request
|
||||
// cadences during steady-state activity.
|
||||
const sessionTickInterval = time.Second
|
||||
|
||||
// metricsConn wraps a net.Conn and tracks per-session byte / write / FBU
|
||||
|
||||
@@ -42,17 +42,17 @@ const (
|
||||
|
||||
// clientNetbirdTypeText is a NetBird-specific message that asks the
|
||||
// server to synthesize the given text as keystrokes regardless of the
|
||||
// active desktop. Used by the dashboard's Paste button to push host
|
||||
// clipboard content into a Windows secure desktop (Winlogon, UAC),
|
||||
// where the OS clipboard is isolated. Format mirrors clientCutText:
|
||||
// 1-byte message type + 3-byte padding + 4-byte length + text bytes.
|
||||
// The opcode is in the vendor-specific range (>=128).
|
||||
// active desktop. Lets a client push host clipboard content into a
|
||||
// Windows secure desktop (Winlogon, UAC), where the OS clipboard is
|
||||
// isolated. Format mirrors clientCutText: 1-byte message type + 3-byte
|
||||
// padding + 4-byte length + text bytes. The opcode is in the
|
||||
// vendor-specific range (>=128).
|
||||
clientNetbirdTypeText = 250
|
||||
|
||||
// clientNetbirdShowRemoteCursor toggles "show remote cursor" mode.
|
||||
// When enabled the encoder composites the server cursor sprite into
|
||||
// the captured framebuffer and suppresses the Cursor pseudo-encoding
|
||||
// so the dashboard sees a single pointer at the remote position.
|
||||
// so the client sees a single pointer at the remote position.
|
||||
// Wire format: 1-byte msgType + 1-byte enable flag + 6 padding bytes
|
||||
// reserved for future arguments (so the message is fixed-size).
|
||||
clientNetbirdShowRemoteCursor = 251
|
||||
@@ -71,13 +71,13 @@ const (
|
||||
// Pseudo-encodings carried over wire as rects with a negative
|
||||
// encoding value. The client advertises supported optional protocol
|
||||
// extensions by listing these in SetEncodings.
|
||||
pseudoEncCursor = -239
|
||||
pseudoEncDesktopSize = -223
|
||||
pseudoEncLastRect = -224
|
||||
pseudoEncQEMUExtendedKeyEvent = -258
|
||||
pseudoEncDesktopName = -307
|
||||
pseudoEncExtendedDesktopSize = -308
|
||||
pseudoEncExtendedMouseButtons = -316
|
||||
pseudoEncCursor = -239
|
||||
pseudoEncDesktopSize = -223
|
||||
pseudoEncLastRect = -224
|
||||
pseudoEncQEMUExtendedKeyEvent = -258
|
||||
pseudoEncDesktopName = -307
|
||||
pseudoEncExtendedDesktopSize = -308
|
||||
pseudoEncExtendedMouseButtons = -316
|
||||
|
||||
// Quality/Compression level pseudo-encodings. The client picks one
|
||||
// value from each range to tune JPEG quality and zlib effort. 0 is
|
||||
@@ -405,10 +405,10 @@ func coalesceRects(in [][4]int) [][4]int {
|
||||
// algorithm can be split across small methods without long parameter lists
|
||||
// and to keep each method's cognitive complexity below Sonar's threshold.
|
||||
type rectCoalescer struct {
|
||||
out [][4]int
|
||||
prevRowStart, prevRowEnd int
|
||||
curRowStart int
|
||||
curY int
|
||||
out [][4]int
|
||||
prevRowStart, prevRowEnd int
|
||||
curRowStart int
|
||||
curY int
|
||||
}
|
||||
|
||||
func newRectCoalescer(capacity int) *rectCoalescer {
|
||||
|
||||
@@ -32,8 +32,8 @@ const (
|
||||
)
|
||||
|
||||
// RFB security-failure reason codes sent to the client. These prefixes are
|
||||
// stable so dashboard integrations can branch on them without parsing
|
||||
// free text. Format: "CODE: human message".
|
||||
// stable so clients can branch on them without parsing free text.
|
||||
// Format: "CODE: human message".
|
||||
const (
|
||||
RejectCodeJWTMissing = "AUTH_JWT_MISSING"
|
||||
RejectCodeJWTExpired = "AUTH_JWT_EXPIRED"
|
||||
@@ -114,10 +114,9 @@ type InputInjector interface {
|
||||
// GetClipboard returns the current system clipboard text.
|
||||
GetClipboard() string
|
||||
// TypeText synthesizes the given text as keystrokes on the active
|
||||
// desktop. Used by the dashboard's Paste button to push host clipboard
|
||||
// content into a secure desktop (Winlogon/UAC) where the clipboard is
|
||||
// isolated. On platforms or sessions without keystroke synthesis it
|
||||
// may be a no-op.
|
||||
// desktop. Used to push host clipboard content into a secure desktop
|
||||
// (Winlogon/UAC) where the clipboard is isolated. On platforms or
|
||||
// sessions without keystroke synthesis it may be a no-op.
|
||||
TypeText(text string)
|
||||
}
|
||||
|
||||
@@ -132,10 +131,11 @@ type JWTConfig struct {
|
||||
// connectionHeader is sent by the client before the RFB handshake to specify
|
||||
// the VNC session mode and authenticate.
|
||||
type connectionHeader struct {
|
||||
mode byte
|
||||
username string
|
||||
jwt string
|
||||
sessionID uint32 // Windows session ID (0 = console/auto)
|
||||
mode byte
|
||||
username string
|
||||
jwt string
|
||||
// sessionID is the Windows session ID; 0 selects the console session.
|
||||
sessionID uint32
|
||||
// width and height request the virtual display geometry for session mode.
|
||||
// Zero means use the default.
|
||||
width uint16
|
||||
@@ -159,9 +159,11 @@ type Server struct {
|
||||
injector InputInjector
|
||||
serviceMode bool
|
||||
disableAuth bool
|
||||
localAddr netip.Addr // NetBird WireGuard IP this server is bound to
|
||||
network netip.Prefix // NetBird overlay network
|
||||
log *log.Entry
|
||||
// localAddr is the NetBird WireGuard IP this server is bound to.
|
||||
localAddr netip.Addr
|
||||
// network is the NetBird overlay network.
|
||||
network netip.Prefix
|
||||
log *log.Entry
|
||||
|
||||
mu sync.Mutex
|
||||
listener net.Listener
|
||||
@@ -173,7 +175,8 @@ type Server struct {
|
||||
jwtExtractor *nbjwt.ClaimsExtractor
|
||||
authorizer *sshauth.Authorizer
|
||||
netstackNet *netstack.Net
|
||||
agentToken []byte // raw token bytes for agent-mode auth
|
||||
// agentToken holds the raw token bytes for agent-mode auth.
|
||||
agentToken []byte
|
||||
|
||||
sessionsMu sync.Mutex
|
||||
sessionSeq uint64
|
||||
@@ -212,14 +215,14 @@ type virtualSessionManager interface {
|
||||
}
|
||||
|
||||
// New creates a VNC server with the given screen capturer and input injector.
|
||||
// Authentication is handled by the dashboard JWT exchange after the RFB
|
||||
// handshake; the protocol-level VNC password scheme is not supported.
|
||||
// Authentication uses a JWT supplied by the client in the connection
|
||||
// header; the protocol-level VNC password scheme is not supported.
|
||||
func New(capturer ScreenCapturer, injector InputInjector) *Server {
|
||||
return &Server{
|
||||
capturer: capturer,
|
||||
injector: injector,
|
||||
authorizer: sshauth.NewAuthorizer(),
|
||||
log: log.WithField("component", "vnc-server"),
|
||||
capturer: capturer,
|
||||
injector: injector,
|
||||
authorizer: sshauth.NewAuthorizer(),
|
||||
log: log.WithField("component", "vnc-server"),
|
||||
sessions: make(map[uint64]ActiveSessionInfo),
|
||||
sessionConns: make(map[uint64]net.Conn),
|
||||
}
|
||||
@@ -576,7 +579,7 @@ func (s *Server) handleConnection(conn net.Conn) {
|
||||
serverH: capturer.Height(),
|
||||
log: connLog,
|
||||
// Virtual sessions run on Xvfb which has no usable cursor source,
|
||||
// so we skip the Cursor pseudo-encoding and let the dashboard's
|
||||
// so we skip the Cursor pseudo-encoding and let the client's
|
||||
// local fallback show instead.
|
||||
disableCursor: header.mode == ModeSession,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func (s *Server) platformSessionManager() virtualSessionManager {
|
||||
// connection to a per-user agent. The agent is spawned lazily on the
|
||||
// first connection (and respawned after a console-user change) via
|
||||
// launchctl asuser, which is the only mechanism that lands a child
|
||||
// inside the user's Aqua session — where WindowServer and TCC grants
|
||||
// inside the user's Aqua session, where WindowServer and TCC grants
|
||||
// for screen capture work.
|
||||
func (s *Server) serviceAcceptLoop() {
|
||||
mgr := newDarwinAgentManager(s.ctx)
|
||||
|
||||
@@ -106,7 +106,7 @@ type session struct {
|
||||
// showRemoteCursor switches the encoder to compositing the server
|
||||
// cursor sprite into the captured framebuffer at the remote position
|
||||
// instead of emitting the Cursor pseudo-encoding. Toggled by the
|
||||
// dashboard via clientNetbirdShowRemoteCursor.
|
||||
// client via clientNetbirdShowRemoteCursor.
|
||||
showRemoteCursor bool
|
||||
// cursorWarnOnce throttles the diagnostic emitted when remote-cursor
|
||||
// compositing falls back to a no-op (capturer cannot supply a sprite
|
||||
@@ -140,9 +140,9 @@ type session struct {
|
||||
// pointerMu guards the cached last cursor position used by
|
||||
// releaseStickyInput so the disconnect-time button-release event
|
||||
// targets the cursor's current spot instead of warping to (0, 0).
|
||||
pointerMu sync.Mutex
|
||||
lastPointerX int
|
||||
lastPointerY int
|
||||
pointerMu sync.Mutex
|
||||
lastPointerX int
|
||||
lastPointerY int
|
||||
}
|
||||
|
||||
type fbRequest struct {
|
||||
@@ -226,8 +226,9 @@ func (s *session) handshake() error {
|
||||
}
|
||||
|
||||
// sendSecurityTypes advertises only secNone. Authentication and access
|
||||
// control are layered on top by the dashboard JWT exchange after the RFB
|
||||
// handshake completes, not by the protocol-level password scheme.
|
||||
// control happen in the NetBird connection header (JWT, mode, username)
|
||||
// that precedes the RFB handshake, not via the protocol-level password
|
||||
// scheme.
|
||||
func (s *session) sendSecurityTypes() error {
|
||||
_, err := s.conn.Write([]byte{1, secNone})
|
||||
return err
|
||||
@@ -502,9 +503,9 @@ func (s *session) handleFBUpdateRequest() error {
|
||||
}
|
||||
|
||||
// SendDesktopName pushes a DesktopName pseudo-encoded update to the
|
||||
// client if it advertised support. Used by the server to keep the
|
||||
// dashboard title in sync with the active session (e.g. username
|
||||
// changes after login on a virtual session).
|
||||
// client if it advertised support. Lets the client keep its window title
|
||||
// in sync with the active session (e.g. username changes after login on
|
||||
// a virtual session).
|
||||
func (s *session) SendDesktopName(name string) error {
|
||||
s.encMu.RLock()
|
||||
supported := s.clientSupportsDesktopName
|
||||
@@ -601,9 +602,9 @@ var stickyModifierKeysyms = [...]uint32{
|
||||
0xffe9, 0xffea, // Alt_L, Alt_R
|
||||
0xffe7, 0xffe8, // Meta_L, Meta_R
|
||||
0xffeb, 0xffec, // Super_L, Super_R
|
||||
0xff7e, // Mode_switch
|
||||
0xfe03, // ISO_Level3_Shift (AltGr)
|
||||
0xffe5, // Caps_Lock (release if user dropped mid-press)
|
||||
0xff7e, // Mode_switch
|
||||
0xfe03, // ISO_Level3_Shift (AltGr)
|
||||
0xffe5, // Caps_Lock (release if user dropped mid-press)
|
||||
}
|
||||
|
||||
// releaseStickyInput synthesizes key-up for modifier keysyms and a
|
||||
|
||||
@@ -220,9 +220,10 @@ func (s *session) writeExtClipMessage(payload []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// handleTypeText handles the NetBird-specific PasteAndType message used by
|
||||
// the dashboard's Paste button. Wire format mirrors CutText: 3-byte
|
||||
// padding + 4-byte length + text bytes.
|
||||
// handleTypeText handles the NetBird-specific PasteAndType message that
|
||||
// pushes host clipboard content as synthesized keystrokes, used to reach
|
||||
// secure desktops where the clipboard is isolated. Wire format mirrors
|
||||
// CutText: 3-byte padding + 4-byte length + text bytes.
|
||||
func (s *session) handleTypeText() error {
|
||||
var header [7]byte
|
||||
if _, err := io.ReadFull(s.conn, header[:]); err != nil {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// handleShowRemoteCursor handles the NetBird-specific RFB message used by
|
||||
// the dashboard to toggle "show remote cursor" mode. Wire format: 1-byte
|
||||
// enable flag (0/1) plus 6 padding bytes reserved for future arguments.
|
||||
// handleShowRemoteCursor handles the NetBird-specific RFB message that
|
||||
// toggles "show remote cursor" mode. Wire format: 1-byte enable flag
|
||||
// (0/1) plus 6 padding bytes reserved for future arguments.
|
||||
func (s *session) handleShowRemoteCursor() error {
|
||||
var data [7]byte
|
||||
if _, err := io.ReadFull(s.conn, data[:]); err != nil {
|
||||
@@ -25,9 +25,8 @@ func (s *session) handleShowRemoteCursor() error {
|
||||
}
|
||||
|
||||
// maybeCompositeCursor blends the current server cursor into img when the
|
||||
// dashboard has enabled "show remote cursor" mode. Returns silently in
|
||||
// every error path: a failed compositing must not stop the regular encode
|
||||
// flow.
|
||||
// client has enabled "show remote cursor" mode. Returns silently in every
|
||||
// error path: a failed compositing must not stop the regular encode flow.
|
||||
func (s *session) maybeCompositeCursor(img *image.RGBA) {
|
||||
s.encMu.RLock()
|
||||
enabled := s.showRemoteCursor
|
||||
|
||||
@@ -28,4 +28,3 @@ func swizzleBGRAtoRGBA(dst, src []byte) {
|
||||
dp[i] = 0xFF000000 | (p & 0x0000FF00) | ((p & 0x00FF0000) >> 16) | ((p & 0x000000FF) << 16)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,12 +101,11 @@ func TestEncodeTightJPEG(t *testing.T) {
|
||||
|
||||
func TestSampledColorCount(t *testing.T) {
|
||||
uniform := makeUniformImage(64, 64, 0x10, 0x20, 0x30)
|
||||
if c := sampledColorCountInto(map[uint32]struct{}{},uniform, 0, 0, 64, 64, 32); c != 1 {
|
||||
if c := sampledColorCountInto(map[uint32]struct{}{}, uniform, 0, 0, 64, 64, 32); c != 1 {
|
||||
t.Fatalf("uniform should be 1 colour, got %d", c)
|
||||
}
|
||||
rnd := makeBenchImage(128, 128, 1)
|
||||
if c := sampledColorCountInto(map[uint32]struct{}{},rnd, 0, 0, 128, 128, 16); c <= 16 {
|
||||
if c := sampledColorCountInto(map[uint32]struct{}{}, rnd, 0, 0, 128, 128, 16); c <= 16 {
|
||||
t.Fatalf("random image should exceed colour cap, got %d", c)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ func (vs *VirtualSession) start() error {
|
||||
return err
|
||||
}
|
||||
|
||||
socketPath := fmt.Sprintf("/tmp/.X11-unix/X%s", vs.display[1:])
|
||||
socketPath := fmt.Sprintf("%s/X%s", x11SocketDir, vs.display[1:])
|
||||
if err := waitForPath(socketPath, 5*time.Second); err != nil {
|
||||
vs.stopXvfb()
|
||||
return fmt.Errorf("wait for X11 socket %s: %w", socketPath, err)
|
||||
@@ -196,7 +196,7 @@ func (vs *VirtualSession) isAlive() bool {
|
||||
return false
|
||||
}
|
||||
// Verify the X socket still exists on disk.
|
||||
socketPath := fmt.Sprintf("/tmp/.X11-unix/X%s", display[1:])
|
||||
socketPath := fmt.Sprintf("%s/X%s", x11SocketDir, display[1:])
|
||||
if _, err := os.Stat(socketPath); err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -590,7 +590,7 @@ func bestSessionCandidate(candidates []sessionCandidate) sessionCandidate {
|
||||
func findFreeDisplay() (string, error) {
|
||||
for n := 50; n < 200; n++ {
|
||||
lockFile := fmt.Sprintf("/tmp/.X%d-lock", n)
|
||||
socketFile := fmt.Sprintf("/tmp/.X11-unix/X%d", n)
|
||||
socketFile := fmt.Sprintf("%s/X%d", x11SocketDir, n)
|
||||
if _, err := os.Stat(lockFile); err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user