mirror of
https://github.com/fosrl/newt.git
synced 2026-09-02 12:11:27 +02:00
Basic native root mode works on linux
This commit is contained in:
45
common.go
45
common.go
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,6 +26,36 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// pingFunc is a function that sends one ICMP echo to dst and returns the RTT.
|
||||
type pingFunc func(dst string, timeout time.Duration) (time.Duration, error)
|
||||
|
||||
// pingNative tests reachability of dst using the host OS ping command.
|
||||
// Used when the main tunnel is a native TUN interface with no netstack.
|
||||
func pingNative(dst string, timeout time.Duration) (time.Duration, error) {
|
||||
timeoutSecs := int(timeout.Seconds())
|
||||
if timeoutSecs < 1 {
|
||||
timeoutSecs = 1
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout+time.Second)
|
||||
defer cancel()
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.CommandContext(ctx, "ping", "-n", "1", "-w", fmt.Sprintf("%d", int(timeout.Milliseconds())), dst)
|
||||
case "darwin":
|
||||
cmd = exec.CommandContext(ctx, "ping", "-c", "1", "-W", fmt.Sprintf("%d", int(timeout.Milliseconds())), dst)
|
||||
default:
|
||||
cmd = exec.CommandContext(ctx, "ping", "-c", "1", "-W", fmt.Sprintf("%d", timeoutSecs), dst)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return 0, fmt.Errorf("native ping to %s failed: %w", dst, err)
|
||||
}
|
||||
return time.Since(start), nil
|
||||
}
|
||||
|
||||
const msgHealthFileWriteFailed = "Failed to write health file: %v"
|
||||
|
||||
func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration, error) {
|
||||
@@ -93,7 +124,7 @@ func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration,
|
||||
}
|
||||
|
||||
// reliablePing performs multiple ping attempts with adaptive timeout
|
||||
func reliablePing(tnet *netstack.Net, dst string, baseTimeout time.Duration, maxAttempts int) (time.Duration, error) {
|
||||
func reliablePing(fn pingFunc, dst string, baseTimeout time.Duration, maxAttempts int) (time.Duration, error) {
|
||||
var lastErr error
|
||||
var totalLatency time.Duration
|
||||
successCount := 0
|
||||
@@ -106,7 +137,7 @@ func reliablePing(tnet *netstack.Net, dst string, baseTimeout time.Duration, max
|
||||
jitter := time.Duration(rand.Intn(100)) * time.Millisecond
|
||||
timeout += jitter
|
||||
|
||||
latency, err := ping(tnet, dst, timeout)
|
||||
latency, err := fn(dst, timeout)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
logger.Debug("Ping attempt %d/%d failed: %v", attempt, maxAttempts, err)
|
||||
@@ -129,7 +160,7 @@ func reliablePing(tnet *netstack.Net, dst string, baseTimeout time.Duration, max
|
||||
return 0, fmt.Errorf("all %d ping attempts failed, last error: %v", maxAttempts, lastErr)
|
||||
}
|
||||
|
||||
func pingWithRetry(tnet *netstack.Net, dst string, timeout time.Duration) (stopChan chan struct{}, err error) {
|
||||
func pingWithRetry(fn pingFunc, dst string, timeout time.Duration) (stopChan chan struct{}, err error) {
|
||||
|
||||
if healthFile != "" {
|
||||
err = os.Remove(healthFile)
|
||||
@@ -150,7 +181,7 @@ func pingWithRetry(tnet *netstack.Net, dst string, timeout time.Duration) (stopC
|
||||
|
||||
// First try with the initial parameters
|
||||
logger.Debug("Ping attempt %d", attempt)
|
||||
if latency, err := ping(tnet, dst, timeout); err == nil {
|
||||
if latency, err := fn(dst, timeout); err == nil {
|
||||
// Successful ping
|
||||
logger.Debug("Ping latency: %v", latency)
|
||||
logger.Info("Tunnel connection to server established successfully!")
|
||||
@@ -176,7 +207,7 @@ func pingWithRetry(tnet *netstack.Net, dst string, timeout time.Duration) (stopC
|
||||
default:
|
||||
logger.Debug("Ping attempt %d", attempt)
|
||||
|
||||
if latency, err := ping(tnet, dst, timeout); err != nil {
|
||||
if latency, err := fn(dst, timeout); err != nil {
|
||||
logger.Warn("Ping attempt %d failed: %v", attempt, err)
|
||||
|
||||
// Increase delay after certain thresholds but cap it
|
||||
@@ -233,7 +264,7 @@ func shouldFireRecovery(consecutiveFailures, failureThreshold int, connectionLos
|
||||
return consecutiveFailures >= failureThreshold && !connectionLost
|
||||
}
|
||||
|
||||
func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Client, tunnelID string) chan struct{} {
|
||||
func startPingCheck(fn pingFunc, serverIP string, client *websocket.Client, tunnelID string) chan struct{} {
|
||||
maxInterval := 6 * time.Second
|
||||
currentInterval := pingInterval
|
||||
consecutiveFailures := 0
|
||||
@@ -274,7 +305,7 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
maxAttempts = 4 // More attempts when connection is unstable
|
||||
}
|
||||
|
||||
latency, err := reliablePing(tnet, serverIP, adaptiveTimeout, maxAttempts)
|
||||
latency, err := reliablePing(fn, serverIP, adaptiveTimeout, maxAttempts)
|
||||
if err != nil {
|
||||
consecutiveFailures++
|
||||
|
||||
|
||||
61
main.go
61
main.go
@@ -1007,10 +1007,8 @@ persistent_keepalive_interval=5`, util.FixKey(privateKey.String()), util.FixKey(
|
||||
if cfgErr := network.ConfigureInterface(nativeMainInterfaceName, wgData.TunnelIP+"/32", mtuInt); cfgErr != nil {
|
||||
logger.Error("Failed to configure native main tunnel interface: %v", cfgErr)
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
if routeErr := network.AddRoutes([]string{wgData.ServerIP + "/32"}, nativeMainInterfaceName); routeErr != nil {
|
||||
logger.Warn("Failed to add route for main tunnel server IP: %v", routeErr)
|
||||
}
|
||||
if routeErr := network.AddRoutes([]string{wgData.ServerIP + "/32"}, nativeMainInterfaceName); routeErr != nil {
|
||||
logger.Warn("Failed to add route for main tunnel server IP: %v", routeErr)
|
||||
}
|
||||
// Set up UAPI so wg(8) can inspect the main tunnel interface
|
||||
if fileUAPI, uapiErr := newtDevice.UapiOpen(nativeMainInterfaceName); uapiErr != nil {
|
||||
@@ -1040,37 +1038,38 @@ persistent_keepalive_interval=5`, util.FixKey(privateKey.String()), util.FixKey(
|
||||
pingWithRetryStopChan = nil
|
||||
}
|
||||
|
||||
if !useNativeMainInterface {
|
||||
// Use reliable ping for initial connection test
|
||||
logger.Debug("Testing initial connection with reliable ping...")
|
||||
lat, err := reliablePing(tnet, wgData.ServerIP, pingTimeout, 5)
|
||||
if err == nil && wgData.PublicKey != "" {
|
||||
telemetry.ObserveTunnelLatency(ctx, wgData.PublicKey, "wireguard", lat.Seconds())
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warn("Initial reliable ping failed, but continuing: %v", err)
|
||||
regResult = "failure"
|
||||
} else {
|
||||
logger.Debug("Initial connection test successful")
|
||||
}
|
||||
|
||||
pingWithRetryStopChan, _ = pingWithRetry(tnet, wgData.ServerIP, pingTimeout)
|
||||
|
||||
// Always mark as connected and start the proxy manager regardless of initial ping result
|
||||
// as the pings will continue in the background
|
||||
if !connected {
|
||||
logger.Debug("Starting ping check")
|
||||
pingStopChan = startPingCheck(tnet, wgData.ServerIP, client, wgData.PublicKey)
|
||||
}
|
||||
// Choose ping implementation: native OS ping for native-main, netstack for normal.
|
||||
var pinger pingFunc
|
||||
if useNativeMainInterface {
|
||||
pinger = pingNative
|
||||
} else {
|
||||
// Native main: no netstack-based ping; write health file directly.
|
||||
if healthFile != "" {
|
||||
if writeErr := os.WriteFile(healthFile, []byte("ok"), 0644); writeErr != nil {
|
||||
logger.Warn(msgHealthFileWriteFailed, writeErr)
|
||||
}
|
||||
pinger = func(dst string, timeout time.Duration) (time.Duration, error) {
|
||||
return ping(tnet, dst, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
// Use reliable ping for initial connection test
|
||||
logger.Debug("Testing initial connection with reliable ping...")
|
||||
lat, err := reliablePing(pinger, wgData.ServerIP, pingTimeout, 5)
|
||||
if err == nil && wgData.PublicKey != "" {
|
||||
telemetry.ObserveTunnelLatency(ctx, wgData.PublicKey, "wireguard", lat.Seconds())
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warn("Initial reliable ping failed, but continuing: %v", err)
|
||||
regResult = "failure"
|
||||
} else {
|
||||
logger.Debug("Initial connection test successful")
|
||||
}
|
||||
|
||||
pingWithRetryStopChan, _ = pingWithRetry(pinger, wgData.ServerIP, pingTimeout)
|
||||
|
||||
// Always mark as connected and start the proxy manager regardless of initial ping result
|
||||
// as the pings will continue in the background
|
||||
if !connected {
|
||||
logger.Debug("Starting ping check")
|
||||
pingStopChan = startPingCheck(pinger, wgData.ServerIP, client, wgData.PublicKey)
|
||||
}
|
||||
|
||||
// Create proxy manager
|
||||
if useNativeMainInterface {
|
||||
pm = proxy.NewProxyManagerNative(wgData.TunnelIP)
|
||||
|
||||
Reference in New Issue
Block a user