mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-08-24 12:08:00 +03:00
Compare commits
3 Commits
dev
...
feat/self-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b337f06c0b | ||
|
|
6498c55262 | ||
|
|
f4dab94b21 |
@@ -131,6 +131,18 @@ void SettingsUiController::clearLogs()
|
||||
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||
}
|
||||
|
||||
bool SettingsUiController::runNetworkDiagnostics()
|
||||
{
|
||||
return Logger::runNetworkDiagnostics();
|
||||
}
|
||||
|
||||
void SettingsUiController::exportNetworkDiagnosticsFile(const QString &fileName)
|
||||
{
|
||||
if (!SystemController::saveFile(fileName, Logger::getNetworkDiagnosticsFile())) {
|
||||
qInfo() << "SettingsUiController::exportNetworkDiagnosticsFile: save or share was cancelled or failed";
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsUiController::backupAppConfig(const QString &fileName)
|
||||
{
|
||||
QByteArray data = m_settingsController->backupAppConfig();
|
||||
|
||||
@@ -51,6 +51,9 @@ public slots:
|
||||
void exportServiceLogsFile(const QString &fileName);
|
||||
void clearLogs();
|
||||
|
||||
bool runNetworkDiagnostics();
|
||||
void exportNetworkDiagnosticsFile(const QString &fileName);
|
||||
|
||||
void backupAppConfig(const QString &fileName);
|
||||
void restoreAppConfig(const QString &fileName);
|
||||
void restoreAppConfigFromData(const QByteArray &data);
|
||||
|
||||
@@ -100,6 +100,45 @@ PageType {
|
||||
showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
|
||||
DividerType {
|
||||
visible: networkDiagnosticsAvailable
|
||||
}
|
||||
|
||||
LabelWithButtonType {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -8
|
||||
|
||||
visible: networkDiagnosticsAvailable
|
||||
|
||||
text: qsTr("Run network diagnostics")
|
||||
leftImageSource: "qrc:/images/controls/scan-line.svg"
|
||||
isSmallLeftImage: true
|
||||
|
||||
clickedFunction: function() {
|
||||
var headerText = qsTr("Run network diagnostics?")
|
||||
var descriptionText = qsTr("This collects local network configuration (adapters, routes, DNS, firewall rules and similar) through the background service and saves it as a new Amnezia-network log file, named with the current date and time. It takes a few seconds and does not open any window.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
var noButtonText = qsTr("Cancel")
|
||||
|
||||
var yesButtonFunction = function() {
|
||||
PageController.showBusyIndicator(true)
|
||||
var success = SettingsController.runNetworkDiagnostics()
|
||||
PageController.showBusyIndicator(false)
|
||||
if (success) {
|
||||
PageController.showNotificationMessage(qsTr("Network diagnostics saved"))
|
||||
} else {
|
||||
PageController.showNotificationMessage(qsTr("Network diagnostics failed"))
|
||||
}
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {
|
||||
|
||||
}
|
||||
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model: logTypes
|
||||
@@ -165,15 +204,20 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool networkDiagnosticsAvailable: Qt.platform.os === "windows" || Qt.platform.os === "linux" || Qt.platform.os === "osx"
|
||||
|
||||
// Show service logs only if this is NOT a macOS build with
|
||||
// Network-Extension (IsMacOsNeBuild is injected from C++ at run-time)
|
||||
// or if this is NOT a mobile build
|
||||
property list<QtObject> logTypes: (IsMacOsNeBuild || GC.isMobile()) ? [
|
||||
clientLogs
|
||||
] : [
|
||||
clientLogs,
|
||||
serviceLogs
|
||||
]
|
||||
property list<QtObject> logTypes: {
|
||||
if (IsMacOsNeBuild || GC.isMobile()) {
|
||||
return [clientLogs]
|
||||
} else if (networkDiagnosticsAvailable) {
|
||||
return [clientLogs, serviceLogs, networkDiagnosticsLog]
|
||||
} else {
|
||||
return [clientLogs, serviceLogs]
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: clientLogs
|
||||
@@ -228,4 +272,29 @@ PageType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: networkDiagnosticsLog
|
||||
|
||||
readonly property string title: qsTr("Network diagnostics")
|
||||
readonly property string description: qsTr("Local network configuration snapshot collected via the background service")
|
||||
readonly property bool isVisible: true
|
||||
readonly property var openLogsHandler: function() {
|
||||
SettingsController.openLogsFolder()
|
||||
}
|
||||
readonly property var exportLogsHandler: function() {
|
||||
var timestamp = Qt.formatDateTime(new Date(), "yyyy-MM-dd_HH-mm-ss")
|
||||
var fileName = SystemController.getFileName(qsTr("Save"),
|
||||
qsTr("Logs files (*.log)"),
|
||||
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/Amnezia-network-" + timestamp,
|
||||
true,
|
||||
".log")
|
||||
if (fileName !== "") {
|
||||
PageController.showBusyIndicator(true)
|
||||
SettingsController.exportNetworkDiagnosticsFile(fileName)
|
||||
PageController.showBusyIndicator(false)
|
||||
PageController.showNotificationMessage(qsTr("Logs file saved"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QMetaEnum>
|
||||
#include <QStandardPaths>
|
||||
#include <QSysInfo>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/utils/utilities.h"
|
||||
@@ -104,6 +105,50 @@ bool Logger::setServiceLogsEnabled(bool enabled)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Logger::runNetworkDiagnostics()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
return IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<QString> reply = iface->runNetworkDiagnostics();
|
||||
// Must exceed the service-side script timeout (30s, networkdiagnostics.cpp)
|
||||
// plus margin for the IPC round-trip.
|
||||
if (!reply.waitForFinished(35000)) {
|
||||
qWarning() << "Logger::runNetworkDiagnostics(): IPC call timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString result = reply.returnValue();
|
||||
if (result.startsWith(QLatin1String("ERROR:"))) {
|
||||
qWarning() << "Logger::runNetworkDiagnostics():" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir().mkpath(userLogsDir());
|
||||
const QString path = newNetworkDiagnosticsFilePath();
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
qWarning() << "Logger::runNetworkDiagnostics(): failed to open" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream ts(&file);
|
||||
ts << QString("===== Amnezia network diagnostics - %1 =====\n%2 (%3)\n%4 %5 %6\n\n")
|
||||
.arg(QDateTime::currentDateTimeUtc().toString("yyyy-MM-dd hh:mm:ss.zzz'Z'"),
|
||||
QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture(),
|
||||
APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||
ts << result << "\n\n";
|
||||
|
||||
qDebug() << "Logger::runNetworkDiagnostics(): saved to" << path;
|
||||
return true;
|
||||
}, []() {
|
||||
qWarning() << "Logger::runNetworkDiagnostics(): Service is not running";
|
||||
return false;
|
||||
});
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Logger::userLogsDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log";
|
||||
@@ -135,6 +180,24 @@ QString Logger::serviceLogsFilePath()
|
||||
return systemLogDir() + QDir::separator() + m_serviceLogFileName;
|
||||
}
|
||||
|
||||
QString Logger::newNetworkDiagnosticsFilePath()
|
||||
{
|
||||
const QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd_HH-mm-ss");
|
||||
return userLogsDir() + QDir::separator() + QStringLiteral("Amnezia-network-%1.log").arg(timestamp);
|
||||
}
|
||||
|
||||
QString Logger::latestNetworkDiagnosticsFilePath()
|
||||
{
|
||||
QDir dir(userLogsDir());
|
||||
// "yyyy-MM-dd_HH-mm-ss" sorts lexicographically in chronological order,
|
||||
// so the last entry in a name-sorted listing is the most recent run.
|
||||
const QStringList files = dir.entryList({ QStringLiteral("Amnezia-network-*.log") }, QDir::Files, QDir::Name);
|
||||
if (files.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
return dir.filePath(files.last());
|
||||
}
|
||||
|
||||
QString Logger::getLogFile()
|
||||
{
|
||||
if (m_file.isOpen()) {
|
||||
@@ -169,6 +232,17 @@ QString Logger::getServiceLogFile()
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Logger::getNetworkDiagnosticsFile()
|
||||
{
|
||||
const QString path = latestNetworkDiagnosticsFilePath();
|
||||
if (path.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
QFile file(path);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
return QString::fromUtf8(file.readAll());
|
||||
}
|
||||
|
||||
bool Logger::openLogsFolder(bool isServiceLogger)
|
||||
{
|
||||
QString path = isServiceLogger ? systemLogDir() : userLogsDir();
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
static void deInit();
|
||||
|
||||
static bool setServiceLogsEnabled(bool enabled);
|
||||
static bool runNetworkDiagnostics();
|
||||
|
||||
static bool openLogsFolder(bool isServiceLogger);
|
||||
|
||||
@@ -29,10 +30,13 @@ public:
|
||||
|
||||
static QString userLogsFilePath();
|
||||
static QString serviceLogsFilePath();
|
||||
static QString newNetworkDiagnosticsFilePath();
|
||||
static QString latestNetworkDiagnosticsFilePath();
|
||||
static QString systemLogDir();
|
||||
|
||||
static QString getLogFile();
|
||||
static QString getServiceLogFile();
|
||||
static QString getNetworkDiagnosticsFile();
|
||||
|
||||
// compat with Mozilla logger
|
||||
Logger(const QString &className)
|
||||
|
||||
@@ -44,6 +44,10 @@ class IpcInterface
|
||||
SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) );
|
||||
SLOT( bool stopNetworkCheck() );
|
||||
|
||||
// Runs the bundled platform diagnostics script and returns the concatenated
|
||||
// section output, or "ERROR: <reason>" on failure.
|
||||
SLOT( QString runNetworkDiagnostics() );
|
||||
|
||||
SIGNAL( connectionLose() );
|
||||
SIGNAL( wakeup() );
|
||||
SIGNAL( networkChanged() );
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "logger.h"
|
||||
#include "router.h"
|
||||
#include "killswitch.h"
|
||||
#include "networkdiagnostics.h"
|
||||
#include "xray.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -241,6 +242,15 @@ bool IpcServer::stopNetworkCheck()
|
||||
return true;
|
||||
}
|
||||
|
||||
QString IpcServer::runNetworkDiagnostics()
|
||||
{
|
||||
#ifdef MZ_DEBUG
|
||||
qDebug() << "IpcServer::runNetworkDiagnostics";
|
||||
#endif
|
||||
|
||||
return NetworkDiagnostics::run();
|
||||
}
|
||||
|
||||
bool IpcServer::resetKillSwitchAllowedRange(QStringList ranges)
|
||||
{
|
||||
#ifdef MZ_DEBUG
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
virtual bool xrayStop() override;
|
||||
virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override;
|
||||
virtual bool stopNetworkCheck() override;
|
||||
virtual QString runNetworkDiagnostics() override;
|
||||
|
||||
private:
|
||||
int m_localpid = 0;
|
||||
|
||||
@@ -32,6 +32,7 @@ set(HEADERS
|
||||
${CMAKE_CURRENT_LIST_DIR}/killswitch.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/systemservice.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/xray.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/networkdiagnostics.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
)
|
||||
|
||||
@@ -49,6 +50,7 @@ set(SOURCES
|
||||
${CMAKE_CURRENT_LIST_DIR}/killswitch.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/xray.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/networkdiagnostics.cpp
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -275,6 +277,10 @@ if(LINUX)
|
||||
|
||||
endif()
|
||||
|
||||
qt6_add_resources(QRC ${QRC}
|
||||
${CMAKE_CURRENT_LIST_DIR}/network_diagnostics/networkDiagnostics.qrc
|
||||
)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../src/qtservice.cmake)
|
||||
|
||||
include_directories(
|
||||
@@ -287,7 +293,7 @@ include_directories(
|
||||
)
|
||||
|
||||
|
||||
add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES})
|
||||
add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC})
|
||||
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus Qt6::Concurrent ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
|
||||
295
service/server/network_diagnostics/network-diagnostics-linux.sh
Normal file
295
service/server/network_diagnostics/network-diagnostics-linux.sh
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env bash
|
||||
# amnezia-baseline-snapshot.sh
|
||||
# Linux network state snapshot for establishing a baseline.
|
||||
# Run with sudo — otherwise some data (socket owners, iptables,
|
||||
# some modules) will be incomplete.
|
||||
#
|
||||
# Usage (three runs):
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label clean
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label awg --endpoint <server IP>
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label xray --endpoint <server IP>
|
||||
#
|
||||
# Then compare the folders, e.g.:
|
||||
# diff snapshot-clean/routes.txt snapshot-awg/routes.txt
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
LABEL="snapshot"
|
||||
ENDPOINT=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--label) LABEL="$2"; shift 2 ;;
|
||||
--label=*) LABEL="${1#*=}"; shift ;;
|
||||
--endpoint) ENDPOINT="$2"; shift 2 ;;
|
||||
--endpoint=*) ENDPOINT="${1#*=}"; shift ;;
|
||||
-h|--help)
|
||||
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
OUT="$(pwd)/snapshot-${LABEL}"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# Runs block function $2, writes a timestamped header plus its output
|
||||
# (stdout+stderr) to $OUT/$1.txt. Errors inside a block don't abort the script.
|
||||
save() {
|
||||
local name="$1" fn="$2" path
|
||||
path="$OUT/$name.txt"
|
||||
{
|
||||
echo "=== $name === $(date -Is)"
|
||||
"$fn" 2>&1
|
||||
} > "$path"
|
||||
echo " [ok] $name"
|
||||
}
|
||||
|
||||
echo "Snapshot '$LABEL' -> $OUT"
|
||||
[ "$(id -u)" -eq 0 ] || echo " [warn] not running as root — some data will be incomplete"
|
||||
|
||||
# --- 1. System ---------------------------------------------------------
|
||||
system_block() {
|
||||
echo "--- OS release ---"
|
||||
cat /etc/os-release 2>/dev/null
|
||||
echo
|
||||
echo "--- Kernel / hostname ---"
|
||||
uname -a
|
||||
have hostnamectl && hostnamectl 2>&1
|
||||
echo
|
||||
echo "IsRoot: $([ "$(id -u)" -eq 0 ] && echo True || echo False)"
|
||||
echo "Init system: $(ps -p 1 -o comm= 2>/dev/null)"
|
||||
}
|
||||
save "system" system_block
|
||||
|
||||
# --- 2. Adapters, including hidden and disabled -----------------------------
|
||||
# Key for detecting an "overwritten driver" and leftover interfaces.
|
||||
adapters_block() {
|
||||
echo "--- ip -d link (all interfaces, including down) ---"
|
||||
ip -d link show
|
||||
echo
|
||||
echo "--- ip addr ---"
|
||||
ip addr show
|
||||
echo
|
||||
echo "--- driver info (ethtool -i) for each interface ---"
|
||||
for p in /sys/class/net/*; do
|
||||
ifc=$(basename "$p")
|
||||
[ "$ifc" = "lo" ] && continue
|
||||
echo "-- $ifc --"
|
||||
if have ethtool; then
|
||||
ethtool -i "$ifc" 2>&1
|
||||
else
|
||||
echo "ethtool not installed"
|
||||
fi
|
||||
done
|
||||
echo
|
||||
echo "--- nmcli device (if NetworkManager is present) ---"
|
||||
have nmcli && nmcli -f all device show 2>&1
|
||||
}
|
||||
save "adapters" adapters_block
|
||||
|
||||
# --- 3. Connection type (cellular / wifi / ethernet) ------------------------
|
||||
link_type_block() {
|
||||
echo "--- type detection via /sys and iw ---"
|
||||
for p in /sys/class/net/*; do
|
||||
ifc=$(basename "$p")
|
||||
[ "$ifc" = "lo" ] && continue
|
||||
t="other"
|
||||
if [ -d "$p/wireless" ] || { have iw && iw dev "$ifc" info >/dev/null 2>&1; }; then
|
||||
t="wifi"
|
||||
elif [[ "$ifc" == wwan* || "$ifc" == ww* || "$ifc" == usb* ]]; then
|
||||
t="cellular"
|
||||
elif [[ "$ifc" == en* || "$ifc" == eth* ]]; then
|
||||
t="ethernet"
|
||||
elif [[ "$ifc" == wg* || "$ifc" == tun* || "$ifc" == tap* ]]; then
|
||||
t="vpn-tunnel"
|
||||
fi
|
||||
operstate=$(cat "$p/operstate" 2>/dev/null)
|
||||
carrier=$(cat "$p/carrier" 2>/dev/null)
|
||||
echo "$ifc: type=$t operstate=$operstate carrier=$carrier"
|
||||
done
|
||||
echo
|
||||
echo "--- NetworkManager connection profiles ---"
|
||||
have nmcli && nmcli connection show 2>&1
|
||||
}
|
||||
save "link-type" link_type_block
|
||||
|
||||
# --- 4. Drivers: kernel modules and network filters --------------------------
|
||||
# Catches the wireguard module, tun/tap, and netfilter hooks used by
|
||||
# split-tunnel solutions.
|
||||
drivers_block() {
|
||||
echo "--- lsmod (all loaded modules) ---"
|
||||
lsmod
|
||||
echo
|
||||
echo "--- VPN/netfilter related modules ---"
|
||||
lsmod | grep -E 'wireguard|^tun|^tap|nf_tables|ip_tables|ip6_tables|nfnetlink|xt_|nft_'
|
||||
echo
|
||||
echo "--- modinfo wireguard (if kernel module, not a userspace implementation) ---"
|
||||
have modinfo && modinfo wireguard 2>&1
|
||||
}
|
||||
save "drivers" drivers_block
|
||||
|
||||
# --- 5. Routes ---------------------------------------------------------
|
||||
routes_block() {
|
||||
echo "--- ip route (main table) ---"
|
||||
ip route show
|
||||
echo
|
||||
echo "--- ip route show table all ---"
|
||||
ip route show table all
|
||||
echo
|
||||
echo "--- ip -6 route ---"
|
||||
ip -6 route show table all
|
||||
echo
|
||||
echo "--- ip rule / ip -6 rule (policy routing) ---"
|
||||
ip rule show
|
||||
ip -6 rule show
|
||||
echo
|
||||
echo "--- default routes only ---"
|
||||
ip route show default
|
||||
ip -6 route show default
|
||||
echo
|
||||
echo "--- interfaces: addresses and indexes ---"
|
||||
ip -o addr show
|
||||
}
|
||||
save "routes" routes_block
|
||||
|
||||
# --- 6. Actual routing stack resolution --------------------------------------
|
||||
# What you can't see in `ip route show`: where the packet will ACTUALLY go.
|
||||
route_resolution_block() {
|
||||
targets=("8.8.8.8" "1.1.1.1")
|
||||
if [ -n "$ENDPOINT" ]; then targets=("$ENDPOINT" "${targets[@]}"); fi
|
||||
for t in "${targets[@]}"; do
|
||||
echo "--- ip route get $t ---"
|
||||
ip route get "$t" 2>&1
|
||||
echo
|
||||
done
|
||||
}
|
||||
save "route-resolution" route_resolution_block
|
||||
|
||||
# --- 7. DNS -----------------------------------------------------------------
|
||||
dns_block() {
|
||||
echo "--- /etc/resolv.conf ---"
|
||||
cat /etc/resolv.conf 2>&1
|
||||
echo
|
||||
echo "--- resolvectl status (systemd-resolved) ---"
|
||||
have resolvectl && resolvectl status 2>&1
|
||||
echo
|
||||
echo "--- resolvectl dns / domain ---"
|
||||
have resolvectl && { resolvectl dns 2>&1; resolvectl domain 2>&1; }
|
||||
echo
|
||||
echo "--- NetworkManager DNS (VPN clients write here via nm) ---"
|
||||
have nmcli && nmcli device show 2>&1 | grep -i 'DNS\|GENERAL.DEVICE'
|
||||
echo
|
||||
echo "--- /etc/nsswitch.conf (hosts line) ---"
|
||||
grep '^hosts' /etc/nsswitch.conf 2>&1
|
||||
}
|
||||
save "dns" dns_block
|
||||
|
||||
# --- 8. IPv6 and localhost ---------------------------------------------
|
||||
ipv6_localhost_block() {
|
||||
echo "--- IP addresses (all families) ---"
|
||||
ip -o addr show
|
||||
echo
|
||||
echo "--- IPv6 disabled via sysctl? ---"
|
||||
sysctl net.ipv6.conf.all.disable_ipv6 net.ipv6.conf.default.disable_ipv6 2>&1
|
||||
echo
|
||||
echo "--- localhost resolution ---"
|
||||
getent hosts localhost
|
||||
getent ahosts localhost
|
||||
echo
|
||||
echo "--- hosts file ---"
|
||||
cat /etc/hosts
|
||||
}
|
||||
save "ipv6-localhost" ipv6_localhost_block
|
||||
|
||||
# --- 9. Proxy: environment and system settings ------------------------------
|
||||
# Key block for Xray: it often works through the system proxy.
|
||||
proxy_block() {
|
||||
echo "--- environment variables ---"
|
||||
env | grep -i proxy
|
||||
echo
|
||||
echo "--- /etc/environment ---"
|
||||
grep -i proxy /etc/environment 2>/dev/null
|
||||
echo
|
||||
echo "--- GNOME gsettings proxy (if present) ---"
|
||||
have gsettings && gsettings get org.gnome.system.proxy mode 2>&1
|
||||
echo
|
||||
echo "--- apt proxy config (if present) ---"
|
||||
cat /etc/apt/apt.conf.d/*proxy* 2>/dev/null
|
||||
}
|
||||
save "proxy" proxy_block
|
||||
|
||||
# --- 10. VPN/antiDPI services and processes ----------------------------
|
||||
services_processes_block() {
|
||||
local pattern='amnezia|wireguard|wg-quick|openvpn|tap|mullvad|tailscale|zapret|nfqws|winws|xray|v2ray|clash|outline|hiddify|proton|nord|express'
|
||||
|
||||
echo "--- systemd units matching pattern ---"
|
||||
if have systemctl; then
|
||||
systemctl list-units --all --type=service --no-legend 2>/dev/null | grep -Ei "$pattern"
|
||||
fi
|
||||
echo
|
||||
echo "--- processes ---"
|
||||
ps aux | grep -Ei "$pattern" | grep -v grep
|
||||
echo
|
||||
echo "--- listening TCP sockets (Xray inbounds) ---"
|
||||
if have ss; then
|
||||
ss -tlnp
|
||||
else
|
||||
netstat -tlnp
|
||||
fi
|
||||
echo
|
||||
echo "--- UDP endpoints (AmneziaWG) ---"
|
||||
if have ss; then
|
||||
ss -ulnp
|
||||
else
|
||||
netstat -ulnp
|
||||
fi
|
||||
}
|
||||
save "services-processes" services_processes_block
|
||||
|
||||
# --- 11. Firewall / netfilter ------------------------------------------------
|
||||
firewall_block() {
|
||||
echo "--- iptables -L -n -v ---"
|
||||
have iptables && iptables -L -n -v 2>&1
|
||||
echo
|
||||
echo "--- ip6tables -L -n -v ---"
|
||||
have ip6tables && ip6tables -L -n -v 2>&1
|
||||
echo
|
||||
echo "--- nft list ruleset (first ~400 lines) ---"
|
||||
have nft && nft list ruleset 2>&1 | head -n 400
|
||||
echo "(output truncated; see netfilter-full.txt for the full dump)"
|
||||
echo
|
||||
echo "--- ufw status ---"
|
||||
have ufw && ufw status verbose 2>&1
|
||||
echo
|
||||
echo "--- firewalld ---"
|
||||
if have firewall-cmd; then
|
||||
firewall-cmd --state 2>&1
|
||||
firewall-cmd --list-all 2>&1
|
||||
fi
|
||||
}
|
||||
save "firewall" firewall_block
|
||||
|
||||
# --- 12. Full netfilter ruleset dump (heavy output, separate file) ----------
|
||||
netfilter_full_block() {
|
||||
echo "--- iptables-save ---"
|
||||
have iptables-save && iptables-save 2>&1
|
||||
echo
|
||||
echo "--- ip6tables-save ---"
|
||||
have ip6tables-save && ip6tables-save 2>&1
|
||||
echo
|
||||
echo "--- nft list ruleset (full) ---"
|
||||
have nft && nft list ruleset 2>&1
|
||||
}
|
||||
save "netfilter-full" netfilter_full_block
|
||||
|
||||
echo
|
||||
echo "Done. Folder: $OUT"
|
||||
echo "Diff of two runs, e.g.:"
|
||||
echo " diff snapshot-clean/routes.txt snapshot-awg/routes.txt"
|
||||
288
service/server/network_diagnostics/network-diagnostics-macos.sh
Normal file
288
service/server/network_diagnostics/network-diagnostics-macos.sh
Normal file
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env bash
|
||||
# amnezia-baseline-snapshot.sh
|
||||
# macOS network state snapshot for establishing a baseline.
|
||||
# Run with sudo — otherwise some data (socket owners, pf rules,
|
||||
# some kext/system_profiler details) will be incomplete.
|
||||
#
|
||||
# Usage (three runs):
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label clean
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label awg --endpoint <server IP>
|
||||
# sudo ./amnezia-baseline-snapshot.sh --label xray --endpoint <server IP>
|
||||
#
|
||||
# Then compare the folders, e.g.:
|
||||
# diff snapshot-clean/routes.txt snapshot-awg/routes.txt
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
LABEL="snapshot"
|
||||
ENDPOINT=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--label) LABEL="$2"; shift 2 ;;
|
||||
--label=*) LABEL="${1#*=}"; shift ;;
|
||||
--endpoint) ENDPOINT="$2"; shift 2 ;;
|
||||
--endpoint=*) ENDPOINT="${1#*=}"; shift ;;
|
||||
-h|--help)
|
||||
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
OUT="$(pwd)/snapshot-${LABEL}"
|
||||
mkdir -p "$OUT"
|
||||
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# Runs block function $2, writes a timestamped header plus its output
|
||||
# (stdout+stderr) to $OUT/$1.txt. Errors inside a block don't abort the script.
|
||||
save() {
|
||||
local name="$1" fn="$2" path
|
||||
path="$OUT/$name.txt"
|
||||
{
|
||||
echo "=== $name === $(date -Iseconds 2>/dev/null || date)"
|
||||
"$fn" 2>&1
|
||||
} > "$path"
|
||||
echo " [ok] $name"
|
||||
}
|
||||
|
||||
echo "Snapshot '$LABEL' -> $OUT"
|
||||
[ "$(id -u)" -eq 0 ] || echo " [warn] not running as root — some data will be incomplete"
|
||||
|
||||
# --- 1. System ---------------------------------------------------------
|
||||
system_block() {
|
||||
echo "--- sw_vers ---"
|
||||
sw_vers 2>&1
|
||||
echo
|
||||
echo "--- uname / hostname ---"
|
||||
uname -a
|
||||
hostname
|
||||
echo
|
||||
echo "IsRoot: $([ "$(id -u)" -eq 0 ] && echo True || echo False)"
|
||||
}
|
||||
save "system" system_block
|
||||
|
||||
# --- 2. Adapters, including hidden and disabled -----------------------------
|
||||
# Key for detecting an "overwritten driver" and leftover interfaces.
|
||||
adapters_block() {
|
||||
echo "--- ifconfig -a (all interfaces, including down) ---"
|
||||
ifconfig -a
|
||||
echo
|
||||
echo "--- networksetup -listallhardwareports ---"
|
||||
have networksetup && networksetup -listallhardwareports 2>&1
|
||||
echo
|
||||
echo "--- networksetup -listallnetworkservices ---"
|
||||
have networksetup && networksetup -listallnetworkservices 2>&1
|
||||
}
|
||||
save "adapters" adapters_block
|
||||
|
||||
# --- 3. Connection type (cellular / wifi / ethernet) ------------------------
|
||||
link_type_block() {
|
||||
echo "--- type detection via ifconfig media / networksetup ---"
|
||||
for ifc in $(ifconfig -l 2>/dev/null); do
|
||||
[ "$ifc" = "lo0" ] && continue
|
||||
t="other"
|
||||
case "$ifc" in
|
||||
en*)
|
||||
if have networksetup && networksetup -getairportnetwork "$ifc" >/dev/null 2>&1; then
|
||||
t="wifi"
|
||||
else
|
||||
t="ethernet-or-wifi"
|
||||
fi
|
||||
;;
|
||||
pdp_ip*|wwan*) t="cellular" ;;
|
||||
utun*|ppp*|ipsec*) t="vpn-tunnel" ;;
|
||||
awg*|wg*) t="vpn-tunnel" ;;
|
||||
esac
|
||||
status=$(ifconfig "$ifc" 2>/dev/null | awk '/status:/{print $2}')
|
||||
echo "$ifc: type=$t status=$status"
|
||||
done
|
||||
echo
|
||||
echo "--- current Wi-Fi network (if any) ---"
|
||||
if have networksetup; then
|
||||
for ifc in $(ifconfig -l 2>/dev/null); do
|
||||
case "$ifc" in en*)
|
||||
networksetup -getairportnetwork "$ifc" 2>&1
|
||||
;; esac
|
||||
done
|
||||
fi
|
||||
echo
|
||||
echo "--- scutil --nwi (primary interface / reachability) ---"
|
||||
have scutil && scutil --nwi 2>&1
|
||||
}
|
||||
save "link-type" link_type_block
|
||||
|
||||
# --- 4. Drivers: kernel extensions and system network config ----------------
|
||||
# Catches the WireGuard/tun kext or sysex, and split-tunnel related extensions.
|
||||
drivers_block() {
|
||||
echo "--- kextstat (legacy kexts, if any) ---"
|
||||
have kextstat && kextstat 2>&1
|
||||
echo
|
||||
echo "--- systemextensionsctl list (modern DriverKit/NetworkExtension sysexes) ---"
|
||||
have systemextensionsctl && systemextensionsctl list 2>&1
|
||||
echo
|
||||
echo "--- VPN/netfilter related kexts ---"
|
||||
have kextstat && kextstat 2>&1 | grep -iE 'wireguard|tun|tap|utun|amnezia'
|
||||
}
|
||||
save "drivers" drivers_block
|
||||
|
||||
# --- 5. Routes ---------------------------------------------------------
|
||||
routes_block() {
|
||||
echo "--- netstat -nr (IPv4 + IPv6 routing table) ---"
|
||||
netstat -nr
|
||||
echo
|
||||
echo "--- netstat -nr -f inet (IPv4 only) ---"
|
||||
netstat -nr -f inet
|
||||
echo
|
||||
echo "--- netstat -nr -f inet6 (IPv6 only) ---"
|
||||
netstat -nr -f inet6
|
||||
echo
|
||||
echo "--- default routes only ---"
|
||||
netstat -nr | awk 'NR==1 || /^default/'
|
||||
echo
|
||||
echo "--- interfaces: addresses ---"
|
||||
ifconfig -a | grep -E '^[a-z]|inet '
|
||||
}
|
||||
save "routes" routes_block
|
||||
|
||||
# --- 6. Actual routing stack resolution --------------------------------------
|
||||
# What you can't see in `netstat -nr`: where the packet will ACTUALLY go.
|
||||
route_resolution_block() {
|
||||
targets=("8.8.8.8" "1.1.1.1")
|
||||
if [ -n "$ENDPOINT" ]; then targets=("$ENDPOINT" "${targets[@]}"); fi
|
||||
for t in "${targets[@]}"; do
|
||||
echo "--- route -n get $t ---"
|
||||
route -n get "$t" 2>&1
|
||||
echo
|
||||
done
|
||||
}
|
||||
save "route-resolution" route_resolution_block
|
||||
|
||||
# --- 7. DNS -----------------------------------------------------------------
|
||||
dns_block() {
|
||||
echo "--- scutil --dns (effective per-interface resolver config) ---"
|
||||
have scutil && scutil --dns 2>&1
|
||||
echo
|
||||
echo "--- /etc/resolv.conf (usually managed/symlinked on macOS) ---"
|
||||
cat /etc/resolv.conf 2>&1
|
||||
echo
|
||||
echo "--- networksetup -getdnsservers per network service ---"
|
||||
if have networksetup; then
|
||||
while IFS= read -r svc; do
|
||||
[[ "$svc" == "An asterisk"* ]] && continue
|
||||
echo "-- $svc --"
|
||||
networksetup -getdnsservers "$svc" 2>&1
|
||||
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
|
||||
fi
|
||||
}
|
||||
save "dns" dns_block
|
||||
|
||||
# --- 8. IPv6 and localhost ---------------------------------------------
|
||||
ipv6_localhost_block() {
|
||||
echo "--- IP addresses (all families) ---"
|
||||
ifconfig -a | grep -E '^[a-z]|inet '
|
||||
echo
|
||||
echo "--- IPv6 disabled? (per-interface sysctl / networksetup) ---"
|
||||
sysctl net.inet6.ip6.forwarding 2>&1
|
||||
if have networksetup; then
|
||||
while IFS= read -r svc; do
|
||||
[[ "$svc" == "An asterisk"* ]] && continue
|
||||
echo "-- $svc --"
|
||||
networksetup -getinfo "$svc" 2>&1 | grep -i ipv6
|
||||
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
|
||||
fi
|
||||
echo
|
||||
echo "--- localhost resolution ---"
|
||||
dscacheutil -q host -a name localhost 2>&1
|
||||
echo
|
||||
echo "--- hosts file ---"
|
||||
cat /etc/hosts
|
||||
}
|
||||
save "ipv6-localhost" ipv6_localhost_block
|
||||
|
||||
# --- 9. Proxy: environment and system settings ------------------------------
|
||||
# Key block for Xray: it often works through the system proxy.
|
||||
proxy_block() {
|
||||
echo "--- environment variables ---"
|
||||
env | grep -i proxy
|
||||
echo
|
||||
echo "--- scutil --proxy (system-wide proxy configuration) ---"
|
||||
have scutil && scutil --proxy 2>&1
|
||||
echo
|
||||
echo "--- networksetup proxy settings per network service ---"
|
||||
if have networksetup; then
|
||||
while IFS= read -r svc; do
|
||||
[[ "$svc" == "An asterisk"* ]] && continue
|
||||
echo "-- $svc --"
|
||||
networksetup -getwebproxy "$svc" 2>&1
|
||||
networksetup -getsecurewebproxy "$svc" 2>&1
|
||||
networksetup -getsocksfirewallproxy "$svc" 2>&1
|
||||
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
|
||||
fi
|
||||
}
|
||||
save "proxy" proxy_block
|
||||
|
||||
# --- 10. VPN/antiDPI services and processes ----------------------------
|
||||
services_processes_block() {
|
||||
local pattern='amnezia|wireguard|wg-quick|openvpn|tap|mullvad|tailscale|zapret|nfqws|winws|xray|v2ray|clash|outline|hiddify|proton|nord|express'
|
||||
|
||||
echo "--- launchctl list matching pattern ---"
|
||||
have launchctl && launchctl list 2>/dev/null | grep -Ei "$pattern"
|
||||
echo
|
||||
echo "--- processes ---"
|
||||
ps aux | grep -Ei "$pattern" | grep -v grep
|
||||
echo
|
||||
echo "--- listening TCP sockets (Xray inbounds) ---"
|
||||
if have lsof; then
|
||||
lsof -nP -iTCP -sTCP:LISTEN 2>&1
|
||||
else
|
||||
netstat -anp tcp 2>&1
|
||||
fi
|
||||
echo
|
||||
echo "--- UDP endpoints (AmneziaWG) ---"
|
||||
if have lsof; then
|
||||
lsof -nP -iUDP 2>&1
|
||||
else
|
||||
netstat -anp udp 2>&1
|
||||
fi
|
||||
}
|
||||
save "services-processes" services_processes_block
|
||||
|
||||
# --- 11. Firewall / packet filter ------------------------------------------------
|
||||
# Amnezia loads its rules under the pf anchor "amn" (see macosfirewall.cpp).
|
||||
firewall_block() {
|
||||
echo "--- pfctl -s info (pf enabled/disabled, stats) ---"
|
||||
have pfctl && pfctl -s info 2>&1
|
||||
echo
|
||||
echo "--- pfctl -s Anchors (all loaded anchors) ---"
|
||||
have pfctl && pfctl -s Anchors 2>&1
|
||||
echo
|
||||
echo "--- pfctl -a amn -s rules (Amnezia's own anchor + sub-anchors) ---"
|
||||
if have pfctl; then
|
||||
anchors=$(pfctl -s Anchors 2>/dev/null | awk '/^amn/ {sub(/\*$/, "", $1); print $1}')
|
||||
for anc in $anchors; do
|
||||
echo "-- anchor: $anc --"
|
||||
pfctl -a "$anc" -s rules 2>&1
|
||||
done
|
||||
fi
|
||||
echo
|
||||
echo "--- pfctl -s rules (rules in the main ruleset, not under any anchor) ---"
|
||||
have pfctl && pfctl -s rules 2>&1
|
||||
echo
|
||||
echo "--- socketfilterfw (Application Firewall) ---"
|
||||
if [ -x /usr/libexec/ApplicationFirewall/socketfilterfw ]; then
|
||||
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>&1
|
||||
/usr/libexec/ApplicationFirewall/socketfilterfw --listapps 2>&1
|
||||
fi
|
||||
}
|
||||
save "firewall" firewall_block
|
||||
|
||||
echo
|
||||
echo "Done. Folder: $OUT"
|
||||
echo "Diff of two runs, e.g.:"
|
||||
echo " diff snapshot-clean/routes.txt snapshot-awg/routes.txt"
|
||||
@@ -0,0 +1,219 @@
|
||||
# network-diagnostics-windows.ps1
|
||||
# Windows network state snapshot for establishing a baseline.
|
||||
# Run in PowerShell AS ADMINISTRATOR.
|
||||
#
|
||||
# Usage (three runs):
|
||||
# .\network-diagnostics-windows.ps1 -Label clean
|
||||
# .\network-diagnostics-windows.ps1 -Label awg -Endpoint <server IP>
|
||||
# .\network-diagnostics-windows.ps1 -Label xray -Endpoint <server IP>
|
||||
#
|
||||
# Then compare the folders, e.g.:
|
||||
# Compare-Object (gc .\snapshot-clean\routes.txt) (gc .\snapshot-awg\routes.txt)
|
||||
|
||||
param(
|
||||
[string]$Label = "snapshot",
|
||||
[string]$Endpoint = ""
|
||||
)
|
||||
|
||||
$out = Join-Path (Get-Location) "snapshot-$Label"
|
||||
New-Item -ItemType Directory -Path $out -Force | Out-Null
|
||||
|
||||
function Save($name, $block) {
|
||||
$path = Join-Path $out "$name.txt"
|
||||
"=== $name === $(Get-Date -Format o)" | Out-File $path -Encoding utf8
|
||||
try { & $block *>&1 | Out-File $path -Append -Encoding utf8 }
|
||||
catch { "ERROR: $($_.Exception.Message)" | Out-File $path -Append -Encoding utf8 }
|
||||
Write-Host " [ok] $name"
|
||||
}
|
||||
|
||||
Write-Host "Snapshot '$Label' -> $out"
|
||||
|
||||
# --- 1. System ---------------------------------------------------------
|
||||
Save "system" {
|
||||
Get-ComputerInfo -Property OsName, OsVersion, OsBuildNumber, CsName |
|
||||
Format-List
|
||||
"IsAdmin: " + ([Security.Principal.WindowsPrincipal]`
|
||||
[Security.Principal.WindowsIdentity]::GetCurrent()`
|
||||
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
# --- 2. Adapters, including hidden and disabled -----------------------------
|
||||
# Key for detecting an "overwritten driver" and leftover interfaces.
|
||||
Save "adapters" {
|
||||
Get-NetAdapter -IncludeHidden |
|
||||
Select-Object Name, InterfaceDescription, ifIndex, Status,
|
||||
AdminStatus, MacAddress, LinkSpeed,
|
||||
DriverProvider, DriverVersion, DriverDate,
|
||||
DriverFileName, DriverInformation, Virtual, Hidden |
|
||||
Format-List
|
||||
}
|
||||
|
||||
# --- 3. Connection type (cellular / wifi / ethernet) ------------------------
|
||||
Save "link-type" {
|
||||
"--- NdisPhysicalMedium (802.3=ethernet, Native802.11=wifi, WirelessWan=cellular) ---"
|
||||
Get-NetAdapter -IncludeHidden |
|
||||
Select-Object Name, InterfaceType, NdisPhysicalMedium, MediaConnectionState |
|
||||
Format-Table -AutoSize
|
||||
"--- Connection profiles ---"
|
||||
Get-NetConnectionProfile |
|
||||
Select-Object Name, InterfaceAlias, NetworkCategory,
|
||||
IPv4Connectivity, IPv6Connectivity |
|
||||
Format-List
|
||||
}
|
||||
|
||||
# --- 4. Drivers: network filters and tunnels --------------------------------
|
||||
# Catches WinDivert-style filters, TAP/Wintun, and split-tunnel drivers.
|
||||
Save "drivers-system" {
|
||||
Get-CimInstance Win32_SystemDriver |
|
||||
Where-Object { $_.PathName -match 'drivers' } |
|
||||
Select-Object Name, DisplayName, State, StartMode, PathName |
|
||||
Sort-Object Name | Format-Table -AutoSize
|
||||
}
|
||||
|
||||
Save "drivers-store" {
|
||||
pnputil /enum-drivers
|
||||
}
|
||||
|
||||
Save "drivers-net-class" {
|
||||
Get-CimInstance Win32_PnPSignedDriver |
|
||||
Where-Object { $_.DeviceClass -eq 'NET' } |
|
||||
Select-Object DeviceName, DriverProviderName, DriverVersion,
|
||||
DriverDate, InfName, IsSigned, Signer |
|
||||
Format-List
|
||||
}
|
||||
|
||||
# --- 5. Routes ---------------------------------------------------------
|
||||
Save "routes" {
|
||||
"--- route print ---"
|
||||
route print
|
||||
"`n--- Get-NetRoute (IPv4+IPv6) ---"
|
||||
Get-NetRoute |
|
||||
Select-Object DestinationPrefix, NextHop, ifIndex, InterfaceAlias,
|
||||
RouteMetric, InterfaceMetric, Protocol, AddressFamily |
|
||||
Sort-Object AddressFamily, DestinationPrefix | Format-Table -AutoSize
|
||||
"`n--- default routes only ---"
|
||||
Get-NetRoute -DestinationPrefix '0.0.0.0/0','::/0' -ErrorAction SilentlyContinue |
|
||||
Format-Table -AutoSize
|
||||
"`n--- interface metrics ---"
|
||||
Get-NetIPInterface |
|
||||
Select-Object ifIndex, InterfaceAlias, AddressFamily,
|
||||
InterfaceMetric, Dhcp, ConnectionState |
|
||||
Sort-Object ifIndex | Format-Table -AutoSize
|
||||
}
|
||||
|
||||
# --- 6. Actual routing stack resolution --------------------------------------
|
||||
# What you can't see in route print: where the packet will ACTUALLY go.
|
||||
Save "route-resolution" {
|
||||
$targets = @('8.8.8.8', '1.1.1.1')
|
||||
if ($Endpoint) { $targets = @($Endpoint) + $targets }
|
||||
foreach ($t in $targets) {
|
||||
"--- Find-NetRoute -RemoteIPAddress $t ---"
|
||||
Find-NetRoute -RemoteIPAddress $t -ErrorAction SilentlyContinue |
|
||||
Select-Object IPAddress, InterfaceAlias, ifIndex,
|
||||
DestinationPrefix, NextHop, RouteMetric |
|
||||
Format-List
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
# --- 7. DNS -----------------------------------------------------------------
|
||||
Save "dns" {
|
||||
"--- configured servers ---"
|
||||
Get-DnsClientServerAddress |
|
||||
Select-Object InterfaceAlias, ifIndex, AddressFamily, ServerAddresses |
|
||||
Format-Table -AutoSize
|
||||
"`n--- global settings ---"
|
||||
Get-DnsClientGlobalSetting | Format-List
|
||||
"`n--- NRPT rules (VPN clients write here) ---"
|
||||
Get-DnsClientNrptPolicy -ErrorAction SilentlyContinue | Format-List
|
||||
Get-DnsClientNrptRule -ErrorAction SilentlyContinue | Format-List
|
||||
"`n--- DoH configuration ---"
|
||||
Get-DnsClientDohServerAddress -ErrorAction SilentlyContinue | Format-Table -AutoSize
|
||||
}
|
||||
|
||||
# --- 8. IPv6 and localhost ---------------------------------------------
|
||||
Save "ipv6-localhost" {
|
||||
"--- IP addresses ---"
|
||||
Get-NetIPAddress |
|
||||
Select-Object InterfaceAlias, IPAddress, PrefixLength,
|
||||
AddressFamily, AddressState, SkipAsSource |
|
||||
Sort-Object AddressFamily | Format-Table -AutoSize
|
||||
"`n--- IPv6 global config ---"
|
||||
netsh interface ipv6 show global
|
||||
"`n--- IPv6 disabled via registry (DisabledComponents) ---"
|
||||
Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters' `
|
||||
-Name DisabledComponents -ErrorAction SilentlyContinue |
|
||||
Select-Object DisabledComponents | Format-List
|
||||
"`n--- localhost resolution ---"
|
||||
Resolve-DnsName localhost -ErrorAction SilentlyContinue | Format-Table -AutoSize
|
||||
"`n--- hosts file ---"
|
||||
Get-Content "$env:SystemRoot\System32\drivers\etc\hosts" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# --- 9. Proxy: system and browser (WinINET) ----------------------------
|
||||
# Key block for Xray: it often works through the system proxy.
|
||||
Save "proxy" {
|
||||
"--- env vars ---"
|
||||
Get-ChildItem Env: |
|
||||
Where-Object { $_.Name -match 'PROXY|proxy' } |
|
||||
Format-Table -AutoSize
|
||||
"`n--- WinINET (HKCU, what the browser sees) ---"
|
||||
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' `
|
||||
-ErrorAction SilentlyContinue |
|
||||
Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL |
|
||||
Format-List
|
||||
"`n--- WinHTTP ---"
|
||||
netsh winhttp show proxy
|
||||
}
|
||||
|
||||
# --- 10. VPN/antiDPI services and processes ----------------------------
|
||||
Save "services-processes" {
|
||||
"--- services matching known patterns ---"
|
||||
Get-Service | Where-Object {
|
||||
$_.Name -match 'amnezia|wireguard|wg|openvpn|tap|mullvad|tailscale|zapret|nfqws|winws|xray|v2ray|clash|outline|hiddify|proton|nord|express'
|
||||
} | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize
|
||||
|
||||
"`n--- processes ---"
|
||||
Get-Process | Where-Object {
|
||||
$_.ProcessName -match 'amnezia|wireguard|openvpn|mullvad|tailscale|nfqws|winws|goodbyedpi|byedpi|xray|v2ray|clash|tun2socks|sing-box'
|
||||
} | Select-Object ProcessName, Id, Path | Format-Table -AutoSize
|
||||
|
||||
"`n--- listening sockets on loopback (Xray inbounds) ---"
|
||||
Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.LocalAddress -in '127.0.0.1','::1','0.0.0.0','::' } |
|
||||
Select-Object LocalAddress, LocalPort, OwningProcess,
|
||||
@{n='Process';e={(Get-Process -Id $_.OwningProcess -EA SilentlyContinue).ProcessName}} |
|
||||
Sort-Object LocalPort | Format-Table -AutoSize
|
||||
|
||||
"`n--- UDP endpoints (AmneziaWG) ---"
|
||||
Get-NetUDPEndpoint -ErrorAction SilentlyContinue |
|
||||
Select-Object LocalAddress, LocalPort, OwningProcess,
|
||||
@{n='Process';e={(Get-Process -Id $_.OwningProcess -EA SilentlyContinue).ProcessName}} |
|
||||
Sort-Object LocalPort | Format-Table -AutoSize
|
||||
}
|
||||
|
||||
# --- 11. Firewall / WFP -----------------------------------------------------
|
||||
Save "firewall" {
|
||||
"--- profiles ---"
|
||||
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction,
|
||||
DefaultOutboundAction | Format-Table -AutoSize
|
||||
"`n--- enabled block rules (short list) ---"
|
||||
Get-NetFirewallRule -Enabled True -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Action -eq 'Block' } |
|
||||
Select-Object DisplayName, Direction, Profile, Owner |
|
||||
Format-Table -AutoSize
|
||||
"`n--- third-party security products ---"
|
||||
Get-CimInstance -Namespace root\SecurityCenter2 -ClassName AntiVirusProduct `
|
||||
-ErrorAction SilentlyContinue |
|
||||
Select-Object displayName, productState | Format-List
|
||||
}
|
||||
|
||||
# --- 12. WFP filters (heavy output, separate file) --------------------------
|
||||
Save "wfp-filters" {
|
||||
netsh wfp show filters file=- 2>$null | Select-Object -First 400
|
||||
"(output truncated; for a full dump: netsh wfp show filters file=wfp.xml)"
|
||||
}
|
||||
|
||||
Write-Host "`nDone. Folder: $out"
|
||||
Write-Host "Diff of two runs, e.g.:"
|
||||
Write-Host " Compare-Object (gc .\snapshot-clean\routes.txt) (gc .\snapshot-awg\routes.txt)"
|
||||
@@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/network_diagnostics">
|
||||
<file>network-diagnostics-linux.sh</file>
|
||||
<file>network-diagnostics-macos.sh</file>
|
||||
<file>network-diagnostics-windows.ps1</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
101
service/server/networkdiagnostics.cpp
Normal file
101
service/server/networkdiagnostics.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "networkdiagnostics.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kProcessTimeoutMsec = 30000;
|
||||
const QString kSnapshotLabel = QStringLiteral("diagnostics");
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const QStringList kSectionOrder = { "system", "adapters", "link-type",
|
||||
"drivers-system", "drivers-store", "drivers-net-class",
|
||||
"routes", "route-resolution", "dns",
|
||||
"ipv6-localhost", "proxy", "services-processes",
|
||||
"firewall", "wfp-filters" };
|
||||
#elif defined(Q_OS_LINUX)
|
||||
const QStringList kSectionOrder = { "system", "adapters", "link-type", "drivers",
|
||||
"routes", "route-resolution", "dns", "ipv6-localhost",
|
||||
"proxy", "services-processes", "firewall", "netfilter-full" };
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QStringList kSectionOrder = { "system", "adapters", "link-type", "drivers",
|
||||
"routes", "route-resolution", "dns", "ipv6-localhost",
|
||||
"proxy", "services-processes", "firewall" };
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
QString NetworkDiagnostics::run()
|
||||
{
|
||||
#if !defined(Q_OS_WIN) && !defined(Q_OS_LINUX) && !defined(Q_OS_MACOS)
|
||||
return QStringLiteral("ERROR: network diagnostics is not supported on this platform");
|
||||
#else
|
||||
QTemporaryDir tempDir;
|
||||
if (!tempDir.isValid()) {
|
||||
return QStringLiteral("ERROR: failed to create secure temp directory");
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const QString resourcePath = QStringLiteral(":/network_diagnostics/network-diagnostics-windows.ps1");
|
||||
const QString scriptPath = tempDir.filePath(QStringLiteral("network-diagnostics-windows.ps1"));
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QString resourcePath = QStringLiteral(":/network_diagnostics/network-diagnostics-macos.sh");
|
||||
const QString scriptPath = tempDir.filePath(QStringLiteral("network-diagnostics-macos.sh"));
|
||||
#else
|
||||
const QString resourcePath = QStringLiteral(":/network_diagnostics/network-diagnostics-linux.sh");
|
||||
const QString scriptPath = tempDir.filePath(QStringLiteral("network-diagnostics-linux.sh"));
|
||||
#endif
|
||||
|
||||
if (!QFile::copy(resourcePath, scriptPath)) {
|
||||
return QStringLiteral("ERROR: failed to extract diagnostics script");
|
||||
}
|
||||
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(tempDir.path()); // scripts write ./snapshot-<label>/ relative to CWD
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
process.start(QStringLiteral("powershell"),
|
||||
{ "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden",
|
||||
"-File", scriptPath, "-Label", kSnapshotLabel });
|
||||
#else
|
||||
process.start(QStringLiteral("/bin/bash"), { scriptPath, "--label", kSnapshotLabel });
|
||||
#endif
|
||||
|
||||
if (!process.waitForStarted(5000)) {
|
||||
return QStringLiteral("ERROR: failed to start diagnostics script (%1)").arg(process.errorString());
|
||||
}
|
||||
if (!process.waitForFinished(kProcessTimeoutMsec)) {
|
||||
process.kill();
|
||||
process.waitForFinished(3000);
|
||||
return QStringLiteral("ERROR: diagnostics script timed out after %1 s").arg(kProcessTimeoutMsec / 1000);
|
||||
}
|
||||
|
||||
const QString snapshotDir = tempDir.filePath(QStringLiteral("snapshot-%1").arg(kSnapshotLabel));
|
||||
QString combined;
|
||||
QTextStream out(&combined);
|
||||
int sectionsFound = 0;
|
||||
for (const QString §ion : kSectionOrder) {
|
||||
QFile sectionFile(QStringLiteral("%1/%2.txt").arg(snapshotDir, section));
|
||||
if (!sectionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
out << "=== " << section << " === (section missing)\n\n";
|
||||
continue;
|
||||
}
|
||||
QString sectionText = QString::fromUtf8(sectionFile.readAll());
|
||||
if (sectionText.startsWith(QChar(0xFEFF))) {
|
||||
sectionText.remove(0, 1);
|
||||
}
|
||||
out << sectionText << "\n";
|
||||
sectionsFound++;
|
||||
}
|
||||
|
||||
if (sectionsFound == 0) {
|
||||
return QStringLiteral("ERROR: diagnostics script produced no output (exit code %1)").arg(process.exitCode());
|
||||
}
|
||||
|
||||
return combined;
|
||||
#endif
|
||||
}
|
||||
15
service/server/networkdiagnostics.h
Normal file
15
service/server/networkdiagnostics.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef NETWORKDIAGNOSTICS_H
|
||||
#define NETWORKDIAGNOSTICS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
// Runs the bundled per-platform network diagnostics script (extracted from a
|
||||
// Qt resource into a securely-created temp dir) and returns its concatenated
|
||||
// section output, or an "ERROR: ..." string on failure.
|
||||
class NetworkDiagnostics
|
||||
{
|
||||
public:
|
||||
static QString run();
|
||||
};
|
||||
|
||||
#endif // NETWORKDIAGNOSTICS_H
|
||||
Reference in New Issue
Block a user