Support IPv6 for the embedded VNC server and browser proxy

This commit is contained in:
Viktor Liu
2026-07-12 18:46:47 +02:00
parent 6c3dac6fa0
commit 4b946ec219
10 changed files with 183 additions and 63 deletions
+9 -14
View File
@@ -20,6 +20,7 @@ import (
nbstatus "github.com/netbirdio/netbird/client/status"
wasmcapture "github.com/netbirdio/netbird/client/wasm/internal/capture"
"github.com/netbirdio/netbird/client/wasm/internal/http"
"github.com/netbirdio/netbird/client/wasm/internal/netutil"
"github.com/netbirdio/netbird/client/wasm/internal/rdp"
"github.com/netbirdio/netbird/client/wasm/internal/ssh"
"github.com/netbirdio/netbird/client/wasm/internal/vnc"
@@ -264,7 +265,7 @@ func performPingTCP(client *netbird.Client, hostname string, port, ipVersion int
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
defer cancel()
network := ipVersionNetwork("tcp", ipVersion)
network := netutil.TCPNetwork(ipVersion)
address := net.JoinHostPort(hostname, fmt.Sprintf("%d", port))
start := time.Now()
@@ -410,7 +411,7 @@ func createGenerateVNCSessionKeyMethod() js.Func {
}
// createVNCProxyMethod creates the VNC proxy method for raw TCP-over-WebSocket bridging.
// JS signature: createVNCProxy(hostname, port, mode?, username?, keySessionID?, sessionID?, width?, height?, peerPublicKey?)
// JS signature: createVNCProxy(hostname, port, mode?, username?, keySessionID?, sessionID?, width?, height?, peerPublicKey?, ipVersion?)
//
// mode: "attach" (default) or "session"
// username: required when mode is "session"
@@ -418,6 +419,7 @@ func createGenerateVNCSessionKeyMethod() js.Func {
// sessionID: Windows session ID (0 = console/auto)
// width/height: requested viewport size for session mode (0 = server default)
// peerPublicKey: base64 X25519 static pubkey of the destination peer (required for auth)
// ipVersion: address family to dial: 4, 6, or 0/omitted for automatic
func createVNCProxyMethod(client *netbird.Client) js.Func {
return js.FuncOf(func(_ js.Value, args []js.Value) any {
params, err := parseVNCProxyArgs(args)
@@ -440,6 +442,7 @@ func createVNCProxyMethod(client *netbird.Client) js.Func {
Height: params.height,
PeerPublicKey: params.peerPublicKey,
KeySessionID: params.keySessionID,
IPVersion: params.ipVersion,
})
})
}
@@ -454,6 +457,7 @@ type vncProxyParams struct {
width uint16
height uint16
peerPublicKey string
ipVersion int
rejectViaPromise bool
}
@@ -540,6 +544,9 @@ func parseVNCProxyOptionalNumbers(args []js.Value, p *vncProxyParams) error {
if len(args) > 8 && args[8].Type() == js.TypeString {
p.peerPublicKey = args[8].String()
}
if len(args) > 9 {
p.ipVersion = jsIPVersion(args[9])
}
return nil
}
@@ -662,18 +669,6 @@ func createSetLogLevelMethod(client *netbird.Client) js.Func {
})
}
// ipVersionNetwork appends "4" or "6" to a base network string (e.g. "tcp" -> "tcp4").
func ipVersionNetwork(base string, ipVersion int) string {
switch ipVersion {
case 4:
return base + "4"
case 6:
return base + "6"
default:
return base
}
}
// jsIPVersion extracts an IP version (4 or 6) from a JS string or number.
func jsIPVersion(v js.Value) int {
switch v.Type() {
+16
View File
@@ -0,0 +1,16 @@
// Package netutil holds small networking helpers shared across the wasm
// client's proxy paths (SSH, VNC, ping).
package netutil
// TCPNetwork maps an IP-version selector to the net package's TCP network
// string: 4 -> "tcp4", 6 -> "tcp6", anything else (0/automatic) -> "tcp".
func TCPNetwork(ipVersion int) string {
switch ipVersion {
case 4:
return "tcp4"
case 6:
return "tcp6"
default:
return "tcp"
}
}
+2 -7
View File
@@ -15,6 +15,7 @@ import (
netbird "github.com/netbirdio/netbird/client/embed"
nbssh "github.com/netbirdio/netbird/client/ssh"
"github.com/netbirdio/netbird/client/wasm/internal/netutil"
)
const (
@@ -64,13 +65,7 @@ func (c *Client) Connect(host string, port int, username, jwtToken string, ipVer
Timeout: sshDialTimeout,
}
network := "tcp"
switch ipVersion {
case 4:
network = "tcp4"
case 6:
network = "tcp6"
}
network := netutil.TCPNetwork(ipVersion)
ctx, cancel := context.WithTimeout(context.Background(), sshDialTimeout)
defer cancel()
+13 -1
View File
@@ -17,6 +17,8 @@ import (
"github.com/flynn/noise"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/client/wasm/internal/netutil"
)
var cryptoRandRead = crand.Read
@@ -133,6 +135,7 @@ type VNCProxy struct {
type vncDestination struct {
address string
network string
mode byte
username string
sessionPriv []byte
@@ -188,6 +191,10 @@ type ProxyRequest struct {
// matching private key is looked up inside wasm and never crosses
// the JS boundary.
KeySessionID string
// IPVersion selects the address family for the dial to the destination:
// 4, 6, or 0 for automatic selection. Mirrors the SSH proxy so the
// dashboard can resolve a peer label to a specific family.
IPVersion int
}
// CreateProxy creates a new proxy endpoint for the given VNC destination.
@@ -207,6 +214,7 @@ func (p *VNCProxy) CreateProxy(req ProxyRequest) js.Value {
dest := vncDestination{
address: address,
network: netutil.TCPNetwork(req.IPVersion),
mode: m,
username: username,
sessionID: sessionID,
@@ -394,7 +402,11 @@ func (p *VNCProxy) connectToVNC(conn *vncConnection) {
ctx, cancel := context.WithTimeout(conn.ctx, vncDialTimeout)
defer cancel()
vncConn, err := p.nbClient.Dial(ctx, "tcp", conn.destination.address)
network := conn.destination.network
if network == "" {
network = "tcp"
}
vncConn, err := p.nbClient.Dial(ctx, network, conn.destination.address)
if err != nil {
log.Errorf("VNC connect to %s: %v", conn.destination.address, err)
// Close the WebSocket so noVNC fires a disconnect event.