[client] Add IPv6 support to ACL manager, USP filter, and forwarder (#5688)

This commit is contained in:
Viktor Liu
2026-04-09 16:56:08 +08:00
committed by GitHub
parent a1e7db2713
commit 1c4e5e71d7
78 changed files with 3606 additions and 1071 deletions

View File

@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"net"
"strconv"
"syscall/js"
"time"
@@ -166,39 +167,58 @@ func createSSHMethod(client *netbird.Client) js.Func {
})
}
var jwtToken string
if len(args) > 3 && !args[3].IsNull() && !args[3].IsUndefined() {
jwtToken = args[3].String()
}
jwtToken, ipVersion := parseSSHOptions(args)
return createPromise(func(resolve, reject js.Value) {
sshClient := ssh.NewClient(client)
if err := sshClient.Connect(host, port, username, jwtToken); err != nil {
jsInterface, err := connectSSH(client, host, port, username, jwtToken, ipVersion)
if err != nil {
reject.Invoke(err.Error())
return
}
if err := sshClient.StartSession(80, 24); err != nil {
if closeErr := sshClient.Close(); closeErr != nil {
log.Errorf("Error closing SSH client: %v", closeErr)
}
reject.Invoke(err.Error())
return
}
jsInterface := ssh.CreateJSInterface(sshClient)
resolve.Invoke(jsInterface)
})
})
}
func performPing(client *netbird.Client, hostname string) {
func parseSSHOptions(args []js.Value) (jwtToken string, ipVersion int) {
if len(args) > 3 && !args[3].IsNull() && !args[3].IsUndefined() {
jwtToken = args[3].String()
}
if len(args) > 4 {
ipVersion = jsIPVersion(args[4])
}
return
}
func connectSSH(client *netbird.Client, host string, port int, username, jwtToken string, ipVersion int) (js.Value, error) {
sshClient := ssh.NewClient(client)
if err := sshClient.Connect(host, port, username, jwtToken, ipVersion); err != nil {
return js.Undefined(), err
}
if err := sshClient.StartSession(80, 24); err != nil {
if closeErr := sshClient.Close(); closeErr != nil {
log.Errorf("Error closing SSH client: %v", closeErr)
}
return js.Undefined(), err
}
return ssh.CreateJSInterface(sshClient), nil
}
func performPing(client *netbird.Client, hostname string, ipVersion int) {
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
defer cancel()
// Default to ping4 to avoid dual-stack ICMP endpoint issues in wireguard-go netstack.
network := "ping4"
if ipVersion == 6 {
network = "ping6"
}
start := time.Now()
conn, err := client.Dial(ctx, "ping", hostname)
conn, err := client.Dial(ctx, network, hostname)
if err != nil {
js.Global().Get("console").Call("log", fmt.Sprintf("Ping to %s failed: %v", hostname, err))
return
@@ -225,27 +245,39 @@ func performPing(client *netbird.Client, hostname string) {
}
latency := time.Since(start)
js.Global().Get("console").Call("log", fmt.Sprintf("Ping to %s: %dms", hostname, latency.Milliseconds()))
remote := conn.RemoteAddr().String()
msg := fmt.Sprintf("Ping to %s: %dms", hostname, latency.Milliseconds())
if remote != hostname {
msg += fmt.Sprintf(" (via %s)", remote)
}
js.Global().Get("console").Call("log", msg)
}
func performPingTCP(client *netbird.Client, hostname string, port int) {
func performPingTCP(client *netbird.Client, hostname string, port, ipVersion int) {
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
defer cancel()
network := ipVersionNetwork("tcp", ipVersion)
address := net.JoinHostPort(hostname, fmt.Sprintf("%d", port))
start := time.Now()
conn, err := client.Dial(ctx, "tcp", address)
conn, err := client.Dial(ctx, network, address)
if err != nil {
js.Global().Get("console").Call("log", fmt.Sprintf("TCP ping to %s failed: %v", address, err))
return
}
latency := time.Since(start)
remote := conn.RemoteAddr().String()
if err := conn.Close(); err != nil {
log.Debugf("failed to close TCP connection: %v", err)
}
js.Global().Get("console").Call("log", fmt.Sprintf("TCP ping to %s succeeded: %dms", address, latency.Milliseconds()))
msg := fmt.Sprintf("TCP ping to %s succeeded: %dms", address, latency.Milliseconds())
if remote != address {
msg += fmt.Sprintf(" (via %s)", remote)
}
js.Global().Get("console").Call("log", msg)
}
// createPingMethod creates the ping method
@@ -262,8 +294,12 @@ func createPingMethod(client *netbird.Client) js.Func {
}
hostname := args[0].String()
var ipVersion int
if len(args) > 1 {
ipVersion = jsIPVersion(args[1])
}
return createPromise(func(resolve, reject js.Value) {
performPing(client, hostname)
performPing(client, hostname, ipVersion)
resolve.Invoke(js.Undefined())
})
})
@@ -290,8 +326,12 @@ func createPingTCPMethod(client *netbird.Client) js.Func {
hostname := args[0].String()
port := args[1].Int()
var ipVersion int
if len(args) > 2 {
ipVersion = jsIPVersion(args[2])
}
return createPromise(func(resolve, reject js.Value) {
performPingTCP(client, hostname, port)
performPingTCP(client, hostname, port, ipVersion)
resolve.Invoke(js.Undefined())
})
})
@@ -464,6 +504,31 @@ 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() {
case js.TypeNumber:
return v.Int()
case js.TypeString:
n, _ := strconv.Atoi(v.String())
return n
default:
return 0
}
}
// createPromise is a helper to create JavaScript promises
func createPromise(handler func(resolve, reject js.Value)) js.Value {
return js.Global().Get("Promise").New(js.FuncOf(func(_ js.Value, promiseArgs []js.Value) any {