diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 394d2862..a814b8e4 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -50,7 +50,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64" - name: Upload build artifacts - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-linux path: | @@ -169,7 +169,7 @@ jobs: fetch-depth: '0' - name: Restore build artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: v2rayN-linux path: ${{ github.workspace }}/v2rayN/Release @@ -190,7 +190,7 @@ jobs: ls -R "$GITHUB_WORKSPACE/dist/rpm" || true - name: Upload RPM artifacts - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-rpm path: dist/rpm/**/*.rpm diff --git a/.github/workflows/build-osx.yml b/.github/workflows/build-osx.yml index f36b2b0e..f16f28bf 100644 --- a/.github/workflows/build-osx.yml +++ b/.github/workflows/build-osx.yml @@ -45,7 +45,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-macos path: | diff --git a/.github/workflows/build-windows-desktop.yml b/.github/workflows/build-windows-desktop.yml index 16f3771f..1a85e546 100644 --- a/.github/workflows/build-windows-desktop.yml +++ b/.github/workflows/build-windows-desktop.yml @@ -45,7 +45,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-windows-desktop path: | diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index d1c2f66a..4c1ce3ea 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -42,7 +42,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts - uses: actions/upload-artifact@v6.0.0 + uses: actions/upload-artifact@v7.0.0 with: name: v2rayN-windows path: | diff --git a/package-debian.sh b/package-debian.sh index 8b15bb06..3b91e7ca 100644 --- a/package-debian.sh +++ b/package-debian.sh @@ -16,7 +16,7 @@ cp -rf $OutputPath "${PackagePath}/opt/v2rayN" echo "When this file exists, app will not store configs under this folder" > "${PackagePath}/opt/v2rayN/NotStoreConfigHere.txt" if [ $Arch = "linux-64" ]; then - Arch2="amd64" + Arch2="amd64" else Arch2="arm64" fi @@ -32,9 +32,8 @@ Depends: libc6 (>= 2.34), fontconfig (>= 2.13.1), desktop-file-utils (>= 0.26), Description: A GUI client for Windows and Linux, support Xray core and sing-box-core and others EOF -cat >"${PackagePath}/DEBIAN/postinst" <<-EOF -if [ ! -s /usr/share/applications/v2rayN.desktop ]; then - cat >/usr/share/applications/v2rayN.desktop<<-END +mkdir -p "${PackagePath}/usr/share/applications" +cat >"${PackagePath}/usr/share/applications/v2rayN.desktop" <<-EOF [Desktop Entry] Name=v2rayN Comment=A GUI client for Windows and Linux, support Xray core and sing-box-core and others @@ -43,10 +42,12 @@ Icon=/opt/v2rayN/v2rayN.png Terminal=false Type=Application Categories=Network;Application; -END -fi +EOF -update-desktop-database +cat >"${PackagePath}/DEBIAN/postinst" <<-'EOF' +set -e +update-desktop-database || true +exit 0 EOF sudo chmod 0755 "${PackagePath}/DEBIAN/postinst" diff --git a/package-rhel.sh b/package-rhel.sh index 507e2b18..1843c850 100644 --- a/package-rhel.sh +++ b/package-rhel.sh @@ -1,45 +1,36 @@ #!/usr/bin/env bash set -euo pipefail -# ====== Require Red Hat Enterprise Linux/FedoraLinux/RockyLinux/AlmaLinux/CentOS ====== -if [[ -r /etc/os-release ]]; then - . /etc/os-release - case "$ID" in - rhel|rocky|almalinux|fedora|centos) - echo "[OK] Detected supported system: $NAME $VERSION_ID" - ;; - *) - echo "[ERROR] Unsupported system: $NAME ($ID)." - echo "This script only supports Red Hat Enterprise Linux/RockyLinux/AlmaLinux/CentOS." - exit 1 - ;; - esac -else - echo "[ERROR] Cannot detect system (missing /etc/os-release)." - exit 1 +# Require Red Hat base branch +. /etc/os-release + +case "${ID:-}" in + rhel|rocky|almalinux|fedora|centos) + echo "Detected supported system: ${NAME:-$ID} ${VERSION_ID:-}" + ;; + *) + echo "Unsupported system: ${NAME:-unknown} (${ID:-unknown})." + echo "This script only supports: RHEL / Rocky / AlmaLinux / Fedora / CentOS." + exit 1 + ;; +esac + +# Kernel version +MIN_KERNEL="6.11" +CURRENT_KERNEL="$(uname -r)" + +lowest="$(printf '%s\n%s\n' "$MIN_KERNEL" "$CURRENT_KERNEL" | sort -V | head -n1)" + +if [[ "$lowest" != "$MIN_KERNEL" ]]; then + echo "Kernel $CURRENT_KERNEL is below $MIN_KERNEL" + exit 1 fi -# ======================== Kernel version check (require >= 6.11) ======================= -MIN_KERNEL_MAJOR=6 -MIN_KERNEL_MINOR=11 -KERNEL_FULL=$(uname -r) -KERNEL_MAJOR=$(echo "$KERNEL_FULL" | cut -d. -f1) -KERNEL_MINOR=$(echo "$KERNEL_FULL" | cut -d. -f2) +echo "[OK] Kernel $CURRENT_KERNEL verified." -echo "[INFO] Detected kernel version: $KERNEL_FULL" - -if (( KERNEL_MAJOR < MIN_KERNEL_MAJOR )) || { (( KERNEL_MAJOR == MIN_KERNEL_MAJOR )) && (( KERNEL_MINOR < MIN_KERNEL_MINOR )); }; then - echo "[ERROR] Kernel $KERNEL_FULL is too old. Requires Linux >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." - echo "Please upgrade your system or use a newer container (e.g. Fedora 42+, RHEL 10+)." - exit 1 -fi - -echo "[OK] Kernel version >= ${MIN_KERNEL_MAJOR}.${MIN_KERNEL_MINOR}." - -# ===== Config & Parse arguments ========================================================= +# Config & Parse arguments VERSION_ARG="${1:-}" # Pass version number like 7.13.8, or leave empty WITH_CORE="both" # Default: bundle both xray+sing-box -AUTOSTART=0 # 1 = enable system-wide autostart (/etc/xdg/autostart) FORCE_NETCORE=0 # --netcore => skip archive bundle, use separate downloads ARCH_OVERRIDE="" # --arch x64|arm64|all (optional compile target) BUILD_FROM="" # --buildfrom 1|2|3 to select channel non-interactively @@ -55,7 +46,6 @@ if [[ -n "${VERSION_ARG:-}" ]]; then shift || true; fi while [[ $# -gt 0 ]]; do case "$1" in --with-core) WITH_CORE="${2:-both}"; shift 2;; - --autostart) AUTOSTART=1; shift;; --xray-ver) XRAY_VER="${2:-}"; shift 2;; --singbox-ver) SING_VER="${2:-}"; shift 2;; --netcore) FORCE_NETCORE=1; shift;; @@ -69,38 +59,26 @@ done # Conflict: version number AND --buildfrom cannot be used together if [[ -n "${VERSION_ARG:-}" && -n "${BUILD_FROM:-}" ]]; then - echo "[ERROR] You cannot specify both an explicit version and --buildfrom at the same time." + echo "You cannot specify both an explicit version and --buildfrom at the same time." echo " Provide either a version (e.g. 7.14.0) OR --buildfrom 1|2|3." exit 1 fi -# ===== Environment check + Dependencies ======================================== +# Check and install dependencies host_arch="$(uname -m)" [[ "$host_arch" == "aarch64" || "$host_arch" == "x86_64" ]] || { echo "Only supports aarch64 / x86_64"; exit 1; } install_ok=0 -case "$ID" in - rhel|rocky|almalinux|centos) - if command -v dnf >/dev/null 2>&1; then - sudo dnf -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ - sudo dnf -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync - install_ok=1 - elif command -v yum >/dev/null 2>&1; then - sudo yum -y install dotnet-sdk-8.0 rpm-build rpmdevtools curl unzip tar rsync || \ - sudo yum -y install dotnet-sdk rpm-build rpmdevtools curl unzip tar rsync - install_ok=1 - fi - ;; - *) - ;; -esac -if [[ "$install_ok" -ne 1 ]]; then - echo "[WARN] Could not auto-install dependencies for '$ID'. Make sure these are available:" - echo " dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on RPM-based distros)" +if command -v dnf >/dev/null 2>&1; then + sudo dnf -y install rpm-build rpmdevtools curl unzip tar jq rsync dotnet-sdk-8.0 \ + && install_ok=1 fi -command -v curl >/dev/null +if [[ "$install_ok" -ne 1 ]]; then + echo "Could not auto-install dependencies for '$ID'. Make sure these are available:" + echo "dotnet-sdk 8.x, curl, unzip, tar, rsync, rpm, rpmdevtools, rpm-build (on Red Hat branch)" +fi # Root directory SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" @@ -119,9 +97,6 @@ if [[ ! -f "$PROJECT" ]]; then fi [[ -f "$PROJECT" ]] || { echo "v2rayN.Desktop.csproj not found"; exit 1; } -# Resolve GUI version & auto checkout -VERSION="" - choose_channel() { # If --buildfrom provided, map it directly and skip interaction. if [[ -n "${BUILD_FROM:-}" ]]; then @@ -135,60 +110,35 @@ choose_channel() { # Print menu to stderr and read from /dev/tty so stdout only carries the token. local ch="latest" sel="" + if [[ -t 0 ]]; then echo "[?] Choose v2rayN release channel:" >&2 echo " 1) Latest (stable) [default]" >&2 echo " 2) Pre-release (preview)" >&2 echo " 3) Keep current (do nothing)" >&2 printf "Enter 1, 2 or 3 [default 1]: " >&2 + if read -r sel /dev/null 2>&1; then - tag="$(printf '%s' "$json" \ - | jq -r '[.[] | select(.prerelease==true)][0].tag_name' 2>/dev/null \ - | sed 's/^v//')" || true - fi - - # 2) Fallback to sed/grep only - if [[ -z "${tag:-}" || "${tag:-}" == "null" ]]; then - tag="$(printf '%s' "$json" \ - | tr '\n' ' ' \ - | sed 's/},[[:space:]]*{/\n/g' \ - | grep -m1 -E '"prerelease"[[:space:]]*:[[:space:]]*true' \ - | grep -Eo '"tag_name"[[:space:]]*:[[:space:]]*"v?[^"]+"' \ - | head -n1 \ - | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v?([^"]+)".*/\1/')" || true - fi - - [[ -n "${tag:-}" && "${tag:-}" != "null" ]] || return 1 - printf '%s\n' "$tag" + curl -fsSL "https://api.github.com/repos/2dust/v2rayN/releases?per_page=20" \ + | jq -re 'first(.[] | select(.prerelease == true) | .tag_name)' \ + | sed 's/^v//' } git_try_checkout() { @@ -196,11 +146,7 @@ git_try_checkout() { local want="$1" ref="" if git rev-parse --git-dir >/dev/null 2>&1; then git fetch --tags --force --prune --depth=1 || true - if git rev-parse "refs/tags/v${want}" >/dev/null 2>&1; then - ref="v${want}" - elif git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then - ref="${want}" - elif git rev-parse --verify "${want}" >/dev/null 2>&1; then + if git rev-parse "refs/tags/${want}" >/dev/null 2>&1; then ref="${want}" fi if [[ -n "$ref" ]]; then @@ -216,88 +162,56 @@ git_try_checkout() { return 1 } +apply_channel_or_keep() { + local ch="$1" tag + + if [[ "$ch" == "keep" ]]; then + echo "[*] Keep current repository state (no checkout)." + VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0+git')" + VERSION="${VERSION#v}" + return 0 + fi + + echo "[*] Resolving ${ch} tag from GitHub releases..." + if [[ "$ch" == "prerelease" ]]; then + tag="$(get_latest_tag_prerelease || true)" + else + tag="$(get_latest_tag_latest || true)" + fi + + [[ -n "$tag" ]] || { echo "Failed to resolve latest tag for channel '${ch}'."; exit 1; } + echo "[*] Latest tag for '${ch}': ${tag}" + git_try_checkout "$tag" || { echo "Failed to checkout '${tag}'."; exit 1; } + VERSION="${tag#v}" +} + if git rev-parse --git-dir >/dev/null 2>&1; then if [[ -n "${VERSION_ARG:-}" ]]; then - echo "[*] Trying to switch v2rayN repo to version: ${VERSION_ARG}" - if git_try_checkout "${VERSION_ARG#v}"; then - VERSION="${VERSION_ARG#v}" + clean_ver="${VERSION_ARG#v}" + if git_try_checkout "$clean_ver"; then + VERSION="$clean_ver" else echo "[WARN] Tag '${VERSION_ARG}' not found." ch="$(choose_channel)" - if [[ "$ch" == "keep" ]]; then - echo "[*] Keep current repository state (no checkout)." - if git describe --tags --abbrev=0 >/dev/null 2>&1; then - VERSION="$(git describe --tags --abbrev=0)" - else - VERSION="0.0.0+git" - fi - VERSION="${VERSION#v}" - else - echo "[*] Resolving ${ch} tag from GitHub releases..." - tag="" - if [[ "$ch" == "prerelease" ]]; then - tag="$(get_latest_tag_prerelease || true)" - if [[ -z "$tag" ]]; then - echo "[WARN] Failed to resolve prerelease tag, falling back to latest." - tag="$(get_latest_tag_latest || true)" - fi - else - tag="$(get_latest_tag_latest || true)" - fi - [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } - echo "[*] Latest tag for '${ch}': ${tag}" - git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } - VERSION="${tag#v}" - fi + apply_channel_or_keep "$ch" fi else ch="$(choose_channel)" - if [[ "$ch" == "keep" ]]; then - echo "[*] Keep current repository state (no checkout)." - if git describe --tags --abbrev=0 >/dev/null 2>&1; then - VERSION="$(git describe --tags --abbrev=0)" - else - VERSION="0.0.0+git" - fi - VERSION="${VERSION#v}" - else - echo "[*] Resolving ${ch} tag from GitHub releases..." - tag="" - if [[ "$ch" == "prerelease" ]]; then - tag="$(get_latest_tag_prerelease || true)" - if [[ -z "$tag" ]]; then - echo "[WARN] Failed to resolve prerelease tag, falling back to latest." - tag="$(get_latest_tag_latest || true)" - fi - else - tag="$(get_latest_tag_latest || true)" - fi - [[ -n "$tag" ]] || { echo "[ERROR] Failed to resolve latest tag for channel '${ch}'."; exit 1; } - echo "[*] Latest tag for '${ch}': ${tag}" - git_try_checkout "$tag" || { echo "[ERROR] Failed to checkout '${tag}'."; exit 1; } - VERSION="${tag#v}" - fi + apply_channel_or_keep "$ch" fi else - echo "[WARN] Current directory is not a git repo; cannot checkout version. Proceeding on current tree." - VERSION="${VERSION_ARG:-}" - if [[ -z "$VERSION" ]]; then - if git describe --tags --abbrev=0 >/dev/null 2>&1; then - VERSION="$(git describe --tags --abbrev=0)" - else - VERSION="0.0.0+git" - fi - fi - VERSION="${VERSION#v}" + echo "Current directory is not a git repo; proceeding on current tree." + VERSION="${VERSION_ARG:-0.0.0}" fi + +VERSION="${VERSION#v}" echo "[*] GUI version resolved as: ${VERSION}" -# ===== Helpers for core/rules download (use RID_DIR for arch sync) ===================== +# Helpers for core download_xray() { - # Download Xray core and install to outdir/xray + # Download Xray core local outdir="$1" ver="${XRAY_VER:-}" url tmp zipname="xray.zip" mkdir -p "$outdir" - if [[ -n "${XRAY_VER:-}" ]]; then ver="${XRAY_VER}"; fi if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/XTLS/Xray-core/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true @@ -316,10 +230,9 @@ download_xray() { } download_singbox() { - # Download sing-box core and install to outdir/sing-box + # Download sing-box local outdir="$1" ver="${SING_VER:-}" url tmp tarname="singbox.tar.gz" bin mkdir -p "$outdir" - if [[ -n "${SING_VER:-}" ]]; then ver="${SING_VER}"; fi if [[ -z "$ver" ]]; then ver="$(curl -fsSL https://api.github.com/repos/SagerNet/sing-box/releases/latest \ | grep -Eo '"tag_name":\s*"v[^"]+"' | sed -E 's/.*"v([^"]+)".*/\1/' | head -n1)" || true @@ -339,7 +252,7 @@ download_singbox() { install -Dm755 "$bin" "$outdir/sing-box" } -# Move geo files to a unified path: outroot/bin +# Move geo files to outroot/bin unify_geo_layout() { local outroot="$1" mkdir -p "$outroot/bin" @@ -351,18 +264,13 @@ unify_geo_layout() { "geoip.metadb" \ ) for n in "${names[@]}"; do - # If file exists under bin/xray/, move it up to bin/ if [[ -f "$outroot/bin/xray/$n" ]]; then mv -f "$outroot/bin/xray/$n" "$outroot/bin/$n" fi - # If file already in bin/, leave it as-is - if [[ -f "$outroot/bin/$n" ]]; then - : - fi done } -# Download geo/rule assets; then unify to bin/ +# Download geo/rule assets download_geo_assets() { local outroot="$1" local bin_dir="$outroot/bin" @@ -396,7 +304,7 @@ download_geo_assets() { "https://raw.githubusercontent.com/2dust/sing-box-rules/rule-set-geosite/$f" || true done - # Unify to bin/ + # Unify to bin unify_geo_layout "$outroot" } @@ -427,7 +335,7 @@ download_v2rayn_bundle() { local nested_dir nested_dir="$(find "$outroot" -maxdepth 1 -type d -name 'v2rayN-linux-*' | head -n1 || true)" - if [[ -n "${nested_dir:-}" && -d "$nested_dir/bin" ]]; then + if [[ -n "$nested_dir" && -d "$nested_dir/bin" ]]; then mkdir -p "$outroot/bin" rsync -a "$nested_dir/bin/" "$outroot/bin/" rm -rf "$nested_dir" @@ -451,7 +359,7 @@ build_for_arch() { case "$short" in x64) rid="linux-x64"; rpm_target="x86_64"; archdir="x86_64" ;; arm64) rid="linux-arm64"; rpm_target="aarch64"; archdir="aarch64" ;; - *) echo "[ERROR] Unknown arch '$short' (use x64|arm64)"; return 1;; + *) echo "Unknown arch '$short' (use x64|arm64)"; return 1;; esac echo "[*] Building for target: $short (RID=$rid, RPM --target $rpm_target)" @@ -464,8 +372,7 @@ build_for_arch() { dotnet publish "$PROJECT" \ -c Release -r "$rid" \ -p:PublishSingleFile=false \ - -p:SelfContained=true \ - -p:IncludeNativeLibrariesForSelfExtract=true + -p:SelfContained=true # Per-arch variables (scoped) local RID_DIR="$rid" @@ -502,28 +409,28 @@ build_for_arch() { mkdir -p "$WORKDIR/$PKGROOT/bin/xray" "$WORKDIR/$PKGROOT/bin/sing_box" # Bundle / cores per-arch + fetch_separate_cores_and_rules() { + local outroot="$1" + + if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then + download_xray "$outroot/bin/xray" || echo "[!] xray download failed (skipped)" + fi + if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then + download_singbox "$outroot/bin/sing_box" || echo "[!] sing-box download failed (skipped)" + fi + download_geo_assets "$outroot" || echo "[!] Geo rules download failed (skipped)" + } + if [[ "$FORCE_NETCORE" -eq 0 ]]; then if download_v2rayn_bundle "$WORKDIR/$PKGROOT"; then echo "[*] Using v2rayN bundle archive." else echo "[*] Bundle failed, fallback to separate core + rules." - if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then - download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" - fi - if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then - download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" - fi - download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" + fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT" fi else echo "[*] --netcore specified: use separate core + rules." - if [[ "$WITH_CORE" == "xray" || "$WITH_CORE" == "both" ]]; then - download_xray "$WORKDIR/$PKGROOT/bin/xray" || echo "[!] xray download failed (skipped)" - fi - if [[ "$WITH_CORE" == "sing-box" || "$WITH_CORE" == "both" ]]; then - download_singbox "$WORKDIR/$PKGROOT/bin/sing_box" || echo "[!] sing-box download failed (skipped)" - fi - download_geo_assets "$WORKDIR/$PKGROOT" || echo "[!] Geo rules download failed (skipped)" + fetch_separate_cores_and_rules "$WORKDIR/$PKGROOT" fi # Tarball @@ -577,12 +484,6 @@ https://github.com/2dust/v2rayN install -dm0755 %{buildroot}/opt/v2rayN cp -a * %{buildroot}/opt/v2rayN/ -install -dm0755 %{buildroot}%{_sysconfdir}/sudoers.d -cat > %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny << 'EOF' -ALL ALL=(ALL) !/home/*/.local/share/v2rayN/bin/mihomo/mihomo -EOF -chmod 0440 %{buildroot}%{_sysconfdir}/sudoers.d/v2rayn-mihomo-deny - # Launcher (prefer native ELF first, then DLL fallback) install -dm0755 %{buildroot}%{_bindir} cat > %{buildroot}%{_bindir}/v2rayn << 'EOF' @@ -636,47 +537,13 @@ fi /opt/v2rayN %{_datadir}/applications/v2rayn.desktop %{_datadir}/icons/hicolor/256x256/apps/v2rayn.png -%config(noreplace) /etc/sudoers.d/v2rayn-mihomo-deny SPEC - # Autostart injection (inside %install) and %files entry - if [[ "$AUTOSTART" -eq 1 ]]; then - awk ' - BEGIN{ins=0} - /^%post$/ && !ins { - print "# --- Autostart (.desktop) ---" - print "install -dm0755 %{buildroot}/etc/xdg/autostart" - print "cat > %{buildroot}/etc/xdg/autostart/v2rayn.desktop << '\''EOF'\''" - print "[Desktop Entry]" - print "Type=Application" - print "Name=v2rayN (Autostart)" - print "Exec=v2rayn" - print "X-GNOME-Autostart-enabled=true" - print "NoDisplay=false" - print "EOF" - ins=1 - } - {print} - ' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE" - - awk ' - BEGIN{infiles=0; done=0} - /^%files$/ {infiles=1} - infiles && done==0 && $0 ~ /%{_datadir}\/icons\/hicolor\/256x256\/apps\/v2rayn\.png/ { - print - print "%config(noreplace) /etc/xdg/autostart/v2rayn.desktop" - done=1 - next - } - {print} - ' "$SPECFILE" > "${SPECFILE}.tmp" && mv "${SPECFILE}.tmp" "$SPECFILE" - fi - # Replace placeholders sed -i "s/__VERSION__/${VERSION}/g" "$SPECFILE" sed -i "s/__PKGROOT__/${PKGROOT}/g" "$SPECFILE" - # Build RPM for this arch (force rpm --target to match compile arch) + # Build RPM for this arch rpmbuild -ba "$SPECFILE" --target "$rpm_target" echo "Build done for $short. RPM at:" @@ -690,33 +557,18 @@ SPEC # ===== Arch selection and build orchestration ========================================= case "${ARCH_OVERRIDE:-}" in - "") - # No --arch: use host architecture - if [[ "$host_arch" == "aarch64" ]]; then - build_for_arch arm64 - else - build_for_arch x64 - fi - ;; - x64|amd64) - build_for_arch x64 - ;; - arm64|aarch64) - build_for_arch arm64 - ;; - all) - BUILT_ALL=1 - # Build x64 and arm64 separately; each package contains its own arch-only binaries. - build_for_arch x64 - build_for_arch arm64 - ;; - *) - echo "[ERROR] Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all." - exit 1 - ;; + all) targets=(x64 arm64); BUILT_ALL=1 ;; + x64|amd64) targets=(x64) ;; + arm64|aarch64) targets=(arm64) ;; + "") targets=($([[ "$host_arch" == "aarch64" ]] && echo arm64 || echo x64)) ;; + *) echo "Unknown --arch '${ARCH_OVERRIDE}'. Use x64|arm64|all."; exit 1 ;; esac -# ===== Final summary if building both arches ========================================== +for arch in "${targets[@]}"; do + build_for_arch "$arch" +done + +# Print Both arches information if [[ "$BUILT_ALL" -eq 1 ]]; then echo "" echo "================ Build Summary (both architectures) ================" @@ -725,7 +577,7 @@ if [[ "$BUILT_ALL" -eq 1 ]]; then echo "$rp" done else - echo "[WARN] No RPMs detected in summary (check build logs above)." + echo "No RPMs detected in summary (check build logs above)." fi - echo "===================================================================" + echo "====================================================================" fi diff --git a/v2rayN/Directory.Build.props b/v2rayN/Directory.Build.props index e7e49e2c..9c4b3432 100644 --- a/v2rayN/Directory.Build.props +++ b/v2rayN/Directory.Build.props @@ -1,7 +1,7 @@ - 7.18.0 + 7.19.0 diff --git a/v2rayN/Directory.Packages.props b/v2rayN/Directory.Packages.props index 51bdd56f..14b16776 100644 --- a/v2rayN/Directory.Packages.props +++ b/v2rayN/Directory.Packages.props @@ -5,13 +5,13 @@ false - - - - + + + + - + @@ -19,9 +19,9 @@ - + - + diff --git a/v2rayN/ServiceLib/Common/Utils.cs b/v2rayN/ServiceLib/Common/Utils.cs index 70365e99..75ef4627 100644 --- a/v2rayN/ServiceLib/Common/Utils.cs +++ b/v2rayN/ServiceLib/Common/Utils.cs @@ -629,12 +629,7 @@ public class Utils { try { - List lstIpEndPoints = new(); - List lstTcpConns = new(); - - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); + var (lstIpEndPoints, lstTcpConns) = GetActiveNetworkInfo(); if (lstIpEndPoints?.FindIndex(it => it.Port == port) >= 0) { @@ -676,6 +671,27 @@ public class Utils return 59090; } + public static (List endpoints, List connections) GetActiveNetworkInfo() + { + var endpoints = new List(); + var connections = new List(); + + try + { + var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + + endpoints.AddRange(ipGlobalProperties.GetActiveTcpListeners()); + endpoints.AddRange(ipGlobalProperties.GetActiveUdpListeners()); + connections.AddRange(ipGlobalProperties.GetActiveTcpConnections()); + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + + return (endpoints, connections); + } + #endregion Speed Test #region Miscellaneous diff --git a/v2rayN/ServiceLib/Global.cs b/v2rayN/ServiceLib/Global.cs index da43069c..60d5a11c 100644 --- a/v2rayN/ServiceLib/Global.cs +++ b/v2rayN/ServiceLib/Global.cs @@ -15,7 +15,6 @@ public class Global public const string CoreConfigFileName = "config.json"; public const string CorePreConfigFileName = "configPre.json"; public const string CoreSpeedtestConfigFileName = "configTest{0}.json"; - public const string CoreMultipleLoadConfigFileName = "configMultipleLoad.json"; public const string ClashMixinConfigFileName = "Mixin.yaml"; public const string NamespaceSample = "ServiceLib.Sample."; @@ -88,7 +87,6 @@ public class Global public const string SingboxLocalDNSTag = "local_local"; public const string SingboxHostsDNSTag = "hosts_dns"; public const string SingboxFakeDNSTag = "fake_dns"; - public const string SingboxEchDNSTag = "ech_dns"; public const int Hysteria2DefaultHopInt = 10; diff --git a/v2rayN/ServiceLib/GlobalUsings.cs b/v2rayN/ServiceLib/GlobalUsings.cs index d38ccc06..4553cbc4 100644 --- a/v2rayN/ServiceLib/GlobalUsings.cs +++ b/v2rayN/ServiceLib/GlobalUsings.cs @@ -24,6 +24,7 @@ global using ServiceLib.Common; global using ServiceLib.Enums; global using ServiceLib.Events; global using ServiceLib.Handler; +global using ServiceLib.Handler.Builder; global using ServiceLib.Handler.Fmt; global using ServiceLib.Handler.SysProxy; global using ServiceLib.Helper; diff --git a/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs new file mode 100644 index 00000000..23a495f8 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Builder/CoreConfigContextBuilder.cs @@ -0,0 +1,331 @@ +namespace ServiceLib.Handler.Builder; + +public record CoreConfigContextBuilderResult(CoreConfigContext Context, NodeValidatorResult ValidatorResult) +{ + public bool Success => ValidatorResult.Success; +} + +public class CoreConfigContextBuilder +{ + /// + /// Builds a for the given node, resolves its proxy map, + /// and processes outbound nodes referenced by routing rules. + /// + public static async Task Build(Config config, ProfileItem node) + { + var runCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); + var coreType = runCoreType == ECoreType.sing_box ? ECoreType.sing_box : ECoreType.Xray; + var context = new CoreConfigContext() + { + Node = node, + RunCoreType = runCoreType, + AllProxiesMap = [], + AppConfig = config, + FullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(coreType), + IsTunEnabled = config.TunModeItem.EnableTun, + SimpleDnsItem = config.SimpleDNSItem, + ProtectDomainList = [], + TunProtectSsPort = 0, + ProxyRelaySsPort = 0, + RawDnsItem = await AppManager.Instance.GetDNSItem(coreType), + RoutingItem = await ConfigHandler.GetDefaultRouting(config), + }; + var validatorResult = NodeValidatorResult.Empty(); + var (actNode, nodeValidatorResult) = await ResolveNodeAsync(context, node); + if (!nodeValidatorResult.Success) + { + return new CoreConfigContextBuilderResult(context, nodeValidatorResult); + } + context = context with { Node = actNode }; + validatorResult.Warnings.AddRange(nodeValidatorResult.Warnings); + if (!(context.RoutingItem?.RuleSet.IsNullOrEmpty() ?? true)) + { + var rules = JsonUtils.Deserialize>(context.RoutingItem?.RuleSet) ?? []; + foreach (var ruleItem in rules.Where(ruleItem => ruleItem.Enabled && !Global.OutboundTags.Contains(ruleItem.OutboundTag))) + { + if (ruleItem.OutboundTag.IsNullOrEmpty()) + { + validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleEmptyOutboundTag, ruleItem.Remarks)); + ruleItem.OutboundTag = Global.ProxyTag; + continue; + } + var ruleOutboundNode = await AppManager.Instance.GetProfileItemViaRemarks(ruleItem.OutboundTag); + if (ruleOutboundNode == null) + { + validatorResult.Warnings.Add(string.Format(ResUI.MsgRoutingRuleOutboundNodeNotFound, ruleItem.Remarks, ruleItem.OutboundTag)); + ruleItem.OutboundTag = Global.ProxyTag; + continue; + } + + var (actRuleNode, ruleNodeValidatorResult) = await ResolveNodeAsync(context, ruleOutboundNode, false); + validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Warnings.Select(w => + string.Format(ResUI.MsgRoutingRuleOutboundNodeWarning, ruleItem.Remarks, ruleItem.OutboundTag, w))); + if (!ruleNodeValidatorResult.Success) + { + validatorResult.Warnings.AddRange(ruleNodeValidatorResult.Errors.Select(e => + string.Format(ResUI.MsgRoutingRuleOutboundNodeError, ruleItem.Remarks, ruleItem.OutboundTag, e))); + ruleItem.OutboundTag = Global.ProxyTag; + continue; + } + + context.AllProxiesMap[$"remark:{ruleItem.OutboundTag}"] = actRuleNode; + } + } + + return new CoreConfigContextBuilderResult(context, validatorResult); + } + + /// + /// Resolves a node into the context, optionally wrapping it in a subscription-level proxy chain. + /// Returns the effective (possibly replaced) node and the validation result. + /// + public static async Task<(ProfileItem, NodeValidatorResult)> ResolveNodeAsync(CoreConfigContext context, + ProfileItem node, + bool includeSubChain = true) + { + if (node.IndexId.IsNullOrEmpty()) + { + return (node, NodeValidatorResult.Empty()); + } + + if (includeSubChain) + { + var (virtualChainNode, chainValidatorResult) = await BuildSubscriptionChainNodeAsync(node); + if (virtualChainNode != null) + { + context.AllProxiesMap[virtualChainNode.IndexId] = virtualChainNode; + var (resolvedNode, resolvedResult) = await ResolveNodeAsync(context, virtualChainNode, false); + resolvedResult.Warnings.InsertRange(0, chainValidatorResult.Warnings); + return (resolvedNode, resolvedResult); + } + // Chain not built but warnings may still exist (e.g. missing profiles) + if (chainValidatorResult.Warnings.Count > 0) + { + var fillResult = await RegisterNodeAsync(context, node); + fillResult.Warnings.InsertRange(0, chainValidatorResult.Warnings); + return (node, fillResult); + } + } + + var registerResult = await RegisterNodeAsync(context, node); + return (node, registerResult); + } + + /// + /// If the node's subscription defines prev/next profiles, creates a virtual + /// node that wraps them together. + /// Returns null as the chain item when no chain is needed. + /// Any warnings (e.g. missing prev/next profile) are returned in the validator result. + /// + private static async Task<(ProfileItem? ChainNode, NodeValidatorResult ValidatorResult)> BuildSubscriptionChainNodeAsync(ProfileItem node) + { + var result = NodeValidatorResult.Empty(); + + if (node.Subid.IsNullOrEmpty()) + { + return (null, result); + } + + var subItem = await AppManager.Instance.GetSubItem(node.Subid); + if (subItem == null) + { + return (null, result); + } + + ProfileItem? prevNode = null; + ProfileItem? nextNode = null; + + if (!subItem.PrevProfile.IsNullOrEmpty()) + { + prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); + if (prevNode == null) + { + result.Warnings.Add(string.Format(ResUI.MsgSubscriptionPrevProfileNotFound, subItem.PrevProfile)); + } + } + if (!subItem.NextProfile.IsNullOrEmpty()) + { + nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); + if (nextNode == null) + { + result.Warnings.Add(string.Format(ResUI.MsgSubscriptionNextProfileNotFound, subItem.NextProfile)); + } + } + + if (prevNode is null && nextNode is null) + { + return (null, result); + } + + // Build new proxy chain node + var chainNode = new ProfileItem() + { + IndexId = $"inner-{Utils.GetGuid(false)}", + ConfigType = EConfigType.ProxyChain, + CoreType = node.CoreType ?? ECoreType.Xray, + }; + List childItems = [prevNode?.IndexId, node.IndexId, nextNode?.IndexId]; + var chainExtraItem = chainNode.GetProtocolExtra() with + { + GroupType = chainNode.ConfigType.ToString(), + ChildItems = string.Join(",", childItems.Where(x => !x.IsNullOrEmpty())), + }; + chainNode.SetProtocolExtra(chainExtraItem); + return (chainNode, result); + } + + /// + /// Dispatches registration to either or + /// based on the node's config type. + /// + private static async Task RegisterNodeAsync(CoreConfigContext context, ProfileItem node) + { + if (node.ConfigType.IsGroupType()) + { + return await RegisterGroupNodeAsync(context, node); + } + else + { + return RegisterSingleNodeAsync(context, node); + } + } + + /// + /// Validates a single (non-group) node and, on success, adds it to the proxy map + /// and records any domain addresses that should bypass the proxy. + /// + private static NodeValidatorResult RegisterSingleNodeAsync(CoreConfigContext context, ProfileItem node) + { + if (node.ConfigType.IsGroupType()) + { + return NodeValidatorResult.Empty(); + } + + var nodeValidatorResult = NodeValidator.Validate(node, context.RunCoreType); + if (!nodeValidatorResult.Success) + { + return nodeValidatorResult; + } + + context.AllProxiesMap[node.IndexId] = node; + + var address = node.Address; + if (Utils.IsDomain(address)) + { + context.ProtectDomainList.Add(address); + } + + if (!node.EchConfigList.IsNullOrEmpty()) + { + var echQuerySni = node.Sni; + if (node.StreamSecurity == Global.StreamSecurity + && node.EchConfigList?.Contains("://") == true) + { + var idx = node.EchConfigList.IndexOf('+'); + echQuerySni = idx > 0 ? node.EchConfigList[..idx] : node.Sni; + } + + if (Utils.IsDomain(echQuerySni)) + { + context.ProtectDomainList.Add(echQuerySni); + } + } + + return nodeValidatorResult; + } + + /// + /// Entry point for registering a group node. Initialises the visited/ancestor sets + /// and delegates to . + /// + private static async Task RegisterGroupNodeAsync(CoreConfigContext context, + ProfileItem node) + { + if (!node.ConfigType.IsGroupType()) + { + return NodeValidatorResult.Empty(); + } + + HashSet ancestors = [node.IndexId]; + HashSet globalVisited = [node.IndexId]; + return await TraverseGroupNodeAsync(context, node, globalVisited, ancestors); + } + + /// + /// Recursively walks the children of a group node, registering valid leaf nodes + /// and nested groups. Detects cycles via and + /// deduplicates shared nodes via . + /// + private static async Task TraverseGroupNodeAsync( + CoreConfigContext context, + ProfileItem node, + HashSet globalVisitedGroup, + HashSet ancestorsGroup) + { + var (groupChildList, _) = await GroupProfileManager.GetChildProfileItems(node); + List childIndexIdList = []; + var childNodeValidatorResult = NodeValidatorResult.Empty(); + foreach (var childNode in groupChildList) + { + if (ancestorsGroup.Contains(childNode.IndexId)) + { + childNodeValidatorResult.Errors.Add( + string.Format(ResUI.MsgGroupCycleDependency, node.Remarks, childNode.Remarks)); + continue; + } + + if (globalVisitedGroup.Contains(childNode.IndexId)) + { + childIndexIdList.Add(childNode.IndexId); + continue; + } + + if (!childNode.ConfigType.IsGroupType()) + { + var childNodeResult = RegisterSingleNodeAsync(context, childNode); + childNodeValidatorResult.Warnings.AddRange(childNodeResult.Warnings.Select(w => + string.Format(ResUI.MsgGroupChildNodeWarning, node.Remarks, childNode.Remarks, w))); + childNodeValidatorResult.Errors.AddRange(childNodeResult.Errors.Select(e => + string.Format(ResUI.MsgGroupChildNodeError, node.Remarks, childNode.Remarks, e))); + if (!childNodeResult.Success) + { + continue; + } + + globalVisitedGroup.Add(childNode.IndexId); + childIndexIdList.Add(childNode.IndexId); + continue; + } + + var newAncestorsGroup = new HashSet(ancestorsGroup) { childNode.IndexId }; + var childGroupResult = + await TraverseGroupNodeAsync(context, childNode, globalVisitedGroup, newAncestorsGroup); + childNodeValidatorResult.Warnings.AddRange(childGroupResult.Warnings.Select(w => + string.Format(ResUI.MsgGroupChildGroupNodeWarning, node.Remarks, childNode.Remarks, w))); + childNodeValidatorResult.Errors.AddRange(childGroupResult.Errors.Select(e => + string.Format(ResUI.MsgGroupChildGroupNodeError, node.Remarks, childNode.Remarks, e))); + if (!childGroupResult.Success) + { + continue; + } + + globalVisitedGroup.Add(childNode.IndexId); + childIndexIdList.Add(childNode.IndexId); + } + + if (childIndexIdList.Count == 0) + { + childNodeValidatorResult.Errors.Add(string.Format(ResUI.MsgGroupNoValidChildNode, node.Remarks)); + return childNodeValidatorResult; + } + else + { + childNodeValidatorResult.Warnings.AddRange(childNodeValidatorResult.Errors); + childNodeValidatorResult.Errors.Clear(); + } + + node.SetProtocolExtra(node.GetProtocolExtra() with { ChildItems = Utils.List2String(childIndexIdList), }); + context.AllProxiesMap[node.IndexId] = node; + return childNodeValidatorResult; + } +} diff --git a/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs new file mode 100644 index 00000000..a6adf1c2 --- /dev/null +++ b/v2rayN/ServiceLib/Handler/Builder/NodeValidator.cs @@ -0,0 +1,175 @@ +namespace ServiceLib.Handler.Builder; + +public record NodeValidatorResult(List Errors, List Warnings) +{ + public bool Success => Errors.Count == 0; + + public static NodeValidatorResult Empty() + { + return new NodeValidatorResult([], []); + } +} + +public class NodeValidator +{ + // Static validator rules + private static readonly HashSet SingboxUnsupportedTransports = + [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; + + private static readonly HashSet SingboxTransportSupportedProtocols = + [EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks]; + + private static readonly HashSet SingboxShadowsocksAllowedTransports = + [nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)]; + + public static NodeValidatorResult Validate(ProfileItem item, ECoreType coreType) + { + var v = new ValidationContext(); + ValidateNodeAndCoreSupport(item, coreType, v); + return v.ToResult(); + } + + private class ValidationContext + { + public List Errors { get; } = []; + public List Warnings { get; } = []; + + public void Error(string message) + { + Errors.Add(message); + } + + public void Warning(string message) + { + Warnings.Add(message); + } + + public void Assert(bool condition, string errorMsg) + { + if (!condition) + { + Error(errorMsg); + } + } + + public NodeValidatorResult ToResult() + { + return new NodeValidatorResult(Errors, Warnings); + } + } + + private static void ValidateNodeAndCoreSupport(ProfileItem item, ECoreType coreType, ValidationContext v) + { + if (item.ConfigType is EConfigType.Custom) + { + return; + } + + if (item.ConfigType.IsGroupType()) + { + // Group logic is handled in ValidateGroupNode + return; + } + + // Basic Property Validation + v.Assert(!item.Address.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Address")); + v.Assert(item.Port is > 0 and <= 65535, string.Format(ResUI.MsgInvalidProperty, "Port")); + + // Network & Core Logic + var net = item.GetNetwork(); + if (coreType == ECoreType.sing_box) + { + var transportError = ValidateSingboxTransport(item.ConfigType, net); + if (transportError != null) + v.Error(transportError); + + if (!Global.SingboxSupportConfigType.Contains(item.ConfigType)) + { + v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.sing_box), item.ConfigType)); + } + } + else if (coreType is ECoreType.Xray) + { + if (!Global.XraySupportConfigType.Contains(item.ConfigType)) + { + v.Error(string.Format(ResUI.MsgCoreNotSupportProtocol, nameof(ECoreType.Xray), item.ConfigType)); + } + } + + // Protocol Specifics + var protocolExtra = item.GetProtocolExtra(); + switch (item.ConfigType) + { + case EConfigType.VMess: + v.Assert(!item.Password.IsNullOrEmpty() && Utils.IsGuidByParse(item.Password), + string.Format(ResUI.MsgInvalidProperty, "Password")); + break; + + case EConfigType.VLESS: + v.Assert( + !item.Password.IsNullOrEmpty() + && (Utils.IsGuidByParse(item.Password) || item.Password.Length <= 30), + string.Format(ResUI.MsgInvalidProperty, "Password") + ); + v.Assert(Global.Flows.Contains(protocolExtra.Flow ?? string.Empty), + string.Format(ResUI.MsgInvalidProperty, "Flow")); + break; + + case EConfigType.Shadowsocks: + v.Assert(!item.Password.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "Password")); + v.Assert( + !string.IsNullOrEmpty(protocolExtra.SsMethod) && + Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod), + string.Format(ResUI.MsgInvalidProperty, "SsMethod")); + break; + } + + // TLS & Security + if (item.StreamSecurity == Global.StreamSecurity) + { + if (!item.Cert.IsNullOrEmpty() && CertPemManager.ParsePemChain(item.Cert).Count == 0 && + !item.CertSha.IsNullOrEmpty()) + { + v.Error(string.Format(ResUI.MsgInvalidProperty, "TLS Certificate")); + } + } + + if (item.StreamSecurity == Global.StreamSecurityReality) + { + v.Assert(!item.PublicKey.IsNullOrEmpty(), string.Format(ResUI.MsgInvalidProperty, "PublicKey")); + } + + if (item.Network == nameof(ETransport.xhttp) && !item.Extra.IsNullOrEmpty()) + { + if (JsonUtils.ParseJson(item.Extra) is null) + { + v.Error(string.Format(ResUI.MsgInvalidProperty, "XHTTP Extra")); + } + } + } + + private static string? ValidateSingboxTransport(EConfigType configType, string net) + { + // sing-box does not support xhttp / kcp transports + if (SingboxUnsupportedTransports.Contains(net)) + { + return string.Format(ResUI.MsgCoreNotSupportNetwork, nameof(ECoreType.sing_box), net); + } + + // sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks + if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp)) + { + return string.Format(ResUI.MsgCoreNotSupportProtocolTransport, + nameof(ECoreType.sing_box), configType.ToString(), net); + } + + // sing-box shadowsocks only supports tcp/ws/quic transports + if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net)) + { + return string.Format(ResUI.MsgCoreNotSupportProtocolTransport, + nameof(ECoreType.sing_box), configType.ToString(), net); + } + + return null; + } +} diff --git a/v2rayN/ServiceLib/Handler/ConfigHandler.cs b/v2rayN/ServiceLib/Handler/ConfigHandler.cs index fde2740b..c70ef5c1 100644 --- a/v2rayN/ServiceLib/Handler/ConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/ConfigHandler.cs @@ -254,6 +254,7 @@ public static class ConfigHandler item.CertSha = profileItem.CertSha; item.EchConfigList = profileItem.EchConfigList; item.EchForceQuery = profileItem.EchForceQuery; + item.Finalmask = profileItem.Finalmask; item.ProtoExtra = profileItem.ProtoExtra; } @@ -1122,6 +1123,7 @@ public static class ConfigHandler && AreEqual(o.Fingerprint, n.Fingerprint) && AreEqual(o.PublicKey, n.PublicKey) && AreEqual(o.ShortId, n.ShortId) + && AreEqual(o.Finalmask, n.Finalmask) && (!remarks || o.Remarks == n.Remarks); static bool AreEqual(string? a, string? b) @@ -1231,44 +1233,65 @@ public static class ConfigHandler /// Server node that might need pre-SOCKS /// Core type being used /// A SOCKS profile item or null if not needed - public static async Task GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) + public static ProfileItem? GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) { + if (node.ConfigType != EConfigType.Custom || !(node.PreSocksPort > 0)) + { + return null; + } ProfileItem? itemSocks = null; - if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) + var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; + itemSocks = new ProfileItem() { - var tun2SocksAddress = node.Address; - if (node.ConfigType.IsGroupType()) - { - var lstAddresses = (await GroupProfileManager.GetAllChildDomainAddresses(node)).ToList(); - if (lstAddresses.Count > 0) - { - tun2SocksAddress = Utils.List2String(lstAddresses); - } - } - itemSocks = new ProfileItem() - { - CoreType = ECoreType.sing_box, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - SpiderX = tun2SocksAddress, // Tun2SocksAddress - Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks) - }; - } - else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0) - { - var preCoreType = AppManager.Instance.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; - itemSocks = new ProfileItem() - { - CoreType = preCoreType, - ConfigType = EConfigType.SOCKS, - Address = Global.Loopback, - Port = node.PreSocksPort.Value, - }; - } - await Task.CompletedTask; + CoreType = preCoreType, + ConfigType = EConfigType.SOCKS, + Address = Global.Loopback, + Port = node.PreSocksPort.Value, + }; return itemSocks; } + public static CoreConfigContext? GetPreSocksCoreConfigContext(CoreConfigContext nodeContext) + { + var config = nodeContext.AppConfig; + var node = nodeContext.Node; + var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); + + var preSocksItem = GetPreSocksItem(config, node, coreType); + if (preSocksItem != null) + { + return nodeContext with { Node = preSocksItem, }; + } + + if ((!nodeContext.IsTunEnabled) + || coreType != ECoreType.Xray + || node.ConfigType == EConfigType.Custom) + { + return null; + } + var tunProtectSsPort = Utils.GetFreePort(); + var proxyRelaySsPort = Utils.GetFreePort(); + var preItem = new ProfileItem() + { + CoreType = ECoreType.sing_box, + ConfigType = EConfigType.Shadowsocks, + Address = Global.Loopback, + Port = proxyRelaySsPort, + Password = Global.None, + }; + preItem.SetProtocolExtra(preItem.GetProtocolExtra() with + { + SsMethod = Global.None, + }); + var preContext = nodeContext with + { + Node = preItem, + TunProtectSsPort = tunProtectSsPort, + ProxyRelaySsPort = proxyRelaySsPort, + }; + return preContext; + } + /// /// Remove servers with invalid test results (timeout) /// Useful for cleaning up subscription lists diff --git a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs index f51e2051..1806ad26 100644 --- a/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs +++ b/v2rayN/ServiceLib/Handler/CoreConfigHandler.cs @@ -7,27 +7,27 @@ public static class CoreConfigHandler { private static readonly string _tag = "CoreConfigHandler"; - public static async Task GenerateClientConfig(ProfileItem node, string? fileName) + public static async Task GenerateClientConfig(CoreConfigContext context, string? fileName) { var config = AppManager.Instance.Config; var result = new RetResult(); + var node = context.Node; if (node.ConfigType == EConfigType.Custom) { result = node.CoreType switch { ECoreType.mihomo => await new CoreConfigClashService(config).GenerateClientCustomConfig(node, fileName), - ECoreType.sing_box => await new CoreConfigSingboxService(config).GenerateClientCustomConfig(node, fileName), _ => await GenerateClientCustomConfig(node, fileName) }; } else if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientConfigContent(node); + result = new CoreConfigSingboxService(context).GenerateClientConfigContent(); } else { - result = await new CoreConfigV2rayService(config).GenerateClientConfigContent(node); + result = new CoreConfigV2rayService(context).GenerateClientConfigContent(); } if (result.Success != true) { @@ -93,13 +93,31 @@ public static class CoreConfigHandler public static async Task GenerateClientSpeedtestConfig(Config config, string fileName, List selecteds, ECoreType coreType) { var result = new RetResult(); + var dummyNode = new ProfileItem + { + CoreType = coreType + }; + var builderResult = await CoreConfigContextBuilder.Build(config, dummyNode); + var context = builderResult.Context; + var ids = selecteds.Where(serverTestItem => !serverTestItem.IndexId.IsNullOrEmpty()) + .Select(serverTestItem => serverTestItem.IndexId); + var nodes = await AppManager.Instance.GetProfileItemsByIndexIds(ids); + foreach (var node in nodes) + { + var (actNode, _) = await CoreConfigContextBuilder.ResolveNodeAsync(context, node, true); + if (node.IndexId == actNode.IndexId) + { + continue; + } + context.ServerTestItemMap[node.IndexId] = actNode.IndexId; + } if (coreType == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(selecteds); + result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(selecteds); } else if (coreType == ECoreType.Xray) { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(selecteds); + result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(selecteds); } if (result.Success != true) { @@ -109,20 +127,21 @@ public static class CoreConfigHandler return result; } - public static async Task GenerateClientSpeedtestConfig(Config config, ProfileItem node, ServerTestItem testItem, string fileName) + public static async Task GenerateClientSpeedtestConfig(Config config, CoreConfigContext context, ServerTestItem testItem, string fileName) { var result = new RetResult(); + var node = context.Node; var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); var port = Utils.GetFreePort(initPort + testItem.QueueNum); testItem.Port = port; if (AppManager.Instance.GetCoreType(node, node.ConfigType) == ECoreType.sing_box) { - result = await new CoreConfigSingboxService(config).GenerateClientSpeedtestConfig(node, port); + result = new CoreConfigSingboxService(context).GenerateClientSpeedtestConfig(port); } else { - result = await new CoreConfigV2rayService(config).GenerateClientSpeedtestConfig(node, port); + result = new CoreConfigV2rayService(context).GenerateClientSpeedtestConfig(port); } if (result.Success != true) { diff --git a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs index bfafce13..b620cfe9 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/BaseFmt.cs @@ -73,6 +73,19 @@ public class BaseFmt { dicQuery.Add("pcs", Utils.UrlEncode(item.CertSha)); } + if (item.Finalmask.IsNotEmpty()) + { + var node = JsonUtils.ParseJson(item.Finalmask); + var finalmask = node != null + ? JsonUtils.Serialize(node, new JsonSerializerOptions + { + WriteIndented = false, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }) + : item.Finalmask; + dicQuery.Add("fm", Utils.UrlEncode(finalmask)); + } dicQuery.Add("type", item.Network.IsNotEmpty() ? item.Network : nameof(ETransport.tcp)); @@ -214,6 +227,24 @@ public class BaseFmt item.EchConfigList = GetQueryDecoded(query, "ech"); item.CertSha = GetQueryDecoded(query, "pcs"); + var finalmaskDecoded = GetQueryDecoded(query, "fm"); + if (finalmaskDecoded.IsNotEmpty()) + { + var node = JsonUtils.ParseJson(finalmaskDecoded); + item.Finalmask = node != null + ? JsonUtils.Serialize(node, new JsonSerializerOptions + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }) + : finalmaskDecoded; + } + else + { + item.Finalmask = string.Empty; + } + if (_allowInsecureArray.Any(k => GetQueryDecoded(query, k) == "1")) { item.AllowInsecure = Global.AllowInsecure.First(); diff --git a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs index 401beda7..aed4548f 100644 --- a/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs +++ b/v2rayN/ServiceLib/Handler/Fmt/Hysteria2Fmt.cs @@ -66,7 +66,7 @@ public class Hysteria2Fmt : BaseFmt if (!item.CertSha.IsNullOrEmpty()) { var sha = item.CertSha; - var idx = sha.IndexOf('~'); + var idx = sha.IndexOf(','); if (idx > 0) { sha = sha[..idx]; diff --git a/v2rayN/ServiceLib/Helper/DownloaderHelper.cs b/v2rayN/ServiceLib/Helper/DownloaderHelper.cs index f4a7ae7f..71c3edbe 100644 --- a/v2rayN/ServiceLib/Helper/DownloaderHelper.cs +++ b/v2rayN/ServiceLib/Helper/DownloaderHelper.cs @@ -24,13 +24,13 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { - Timeout = timeout * 1000, + BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { Headers = headers, UserAgent = userAgent, - Timeout = timeout * 1000, + ConnectTimeout = timeout * 1000, Proxy = webProxy } }; @@ -62,11 +62,11 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { - Timeout = timeout * 1000, + BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { - Timeout= timeout * 1000, + ConnectTimeout= timeout * 1000, Proxy = webProxy } }; @@ -139,11 +139,11 @@ public class DownloaderHelper var downloadOpt = new DownloadConfiguration() { - Timeout = timeout * 1000, + BlockTimeout = timeout * 1000, MaxTryAgainOnFailure = 2, RequestConfiguration = { - Timeout= timeout * 1000, + ConnectTimeout= timeout * 1000, Proxy = webProxy } }; diff --git a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs b/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs deleted file mode 100644 index ee3cd5a0..00000000 --- a/v2rayN/ServiceLib/Manager/ActionPrecheckManager.cs +++ /dev/null @@ -1,352 +0,0 @@ -namespace ServiceLib.Manager; - -/// -/// Centralized pre-checks before sensitive actions (set active profile, generate config, etc.). -/// -public class ActionPrecheckManager -{ - private static readonly Lazy _instance = new(); - public static ActionPrecheckManager Instance => _instance.Value; - - // sing-box supported transports for different protocol types - private static readonly HashSet SingboxUnsupportedTransports = [nameof(ETransport.kcp), nameof(ETransport.xhttp)]; - - private static readonly HashSet SingboxTransportSupportedProtocols = - [EConfigType.VMess, EConfigType.VLESS, EConfigType.Trojan, EConfigType.Shadowsocks]; - - private static readonly HashSet SingboxShadowsocksAllowedTransports = - [nameof(ETransport.tcp), nameof(ETransport.ws), nameof(ETransport.quic)]; - - public async Task> Check(string? indexId) - { - if (indexId.IsNullOrEmpty()) - { - return [ResUI.PleaseSelectServer]; - } - - var item = await AppManager.Instance.GetProfileItem(indexId); - if (item is null) - { - return [ResUI.PleaseSelectServer]; - } - - return await Check(item); - } - - public async Task> Check(ProfileItem? item) - { - if (item is null) - { - return [ResUI.PleaseSelectServer]; - } - - var errors = new List(); - - errors.AddRange(await ValidateCurrentNodeAndCoreSupport(item)); - errors.AddRange(await ValidateRelatedNodesExistAndValid(item)); - - return errors; - } - - private async Task> ValidateCurrentNodeAndCoreSupport(ProfileItem item) - { - if (item.ConfigType == EConfigType.Custom) - { - return []; - } - - var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); - return await ValidateNodeAndCoreSupport(item, coreType); - } - - private async Task> ValidateNodeAndCoreSupport(ProfileItem item, ECoreType? coreType = null) - { - var errors = new List(); - - coreType ??= AppManager.Instance.GetCoreType(item, item.ConfigType); - - if (item.ConfigType is EConfigType.Custom) - { - errors.Add(string.Format(ResUI.NotSupportProtocol, item.ConfigType.ToString())); - return errors; - } - else if (item.ConfigType.IsGroupType()) - { - var groupErrors = await ValidateGroupNode(item, coreType); - errors.AddRange(groupErrors); - return errors; - } - else if (!item.IsComplex()) - { - var normalErrors = await ValidateNormalNode(item, coreType); - errors.AddRange(normalErrors); - return errors; - } - - return errors; - } - - private async Task> ValidateNormalNode(ProfileItem item, ECoreType? coreType = null) - { - var errors = new List(); - - if (item.Address.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Address")); - return errors; - } - - if (item.Port is <= 0 or > 65535) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Port")); - return errors; - } - - var net = item.GetNetwork(); - - if (coreType == ECoreType.sing_box) - { - var transportError = ValidateSingboxTransport(item.ConfigType, net); - if (transportError != null) - { - errors.Add(transportError); - } - - if (!Global.SingboxSupportConfigType.Contains(item.ConfigType)) - { - errors.Add(string.Format(ResUI.CoreNotSupportProtocol, - nameof(ECoreType.sing_box), item.ConfigType.ToString())); - } - } - else if (coreType is ECoreType.Xray) - { - // Xray core does not support these protocols - if (!Global.XraySupportConfigType.Contains(item.ConfigType)) - { - errors.Add(string.Format(ResUI.CoreNotSupportProtocol, - nameof(ECoreType.Xray), item.ConfigType.ToString())); - } - } - - var protocolExtra = item.GetProtocolExtra(); - - switch (item.ConfigType) - { - case EConfigType.VMess: - if (item.Password.IsNullOrEmpty() || !Utils.IsGuidByParse(item.Password)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Password")); - } - - break; - - case EConfigType.VLESS: - if (item.Password.IsNullOrEmpty() || (!Utils.IsGuidByParse(item.Password) && item.Password.Length > 30)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Password")); - } - - if (!Global.Flows.Contains(protocolExtra.Flow ?? string.Empty)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Flow")); - } - - break; - - case EConfigType.Shadowsocks: - if (item.Password.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "Password")); - } - - if (string.IsNullOrEmpty(protocolExtra.SsMethod) || !Global.SsSecuritiesInSingbox.Contains(protocolExtra.SsMethod)) - { - errors.Add(string.Format(ResUI.InvalidProperty, "SsMethod")); - } - - break; - } - - if (item.StreamSecurity == Global.StreamSecurity) - { - // check certificate validity - if (!item.Cert.IsNullOrEmpty() - && (CertPemManager.ParsePemChain(item.Cert).Count == 0) - && !item.CertSha.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "TLS Certificate")); - } - } - - if (item.StreamSecurity == Global.StreamSecurityReality) - { - if (item.PublicKey.IsNullOrEmpty()) - { - errors.Add(string.Format(ResUI.InvalidProperty, "PublicKey")); - } - } - - if (item.Network == nameof(ETransport.xhttp) - && !item.Extra.IsNullOrEmpty()) - { - // check xhttp extra json validity - var xhttpExtra = JsonUtils.ParseJson(item.Extra); - if (xhttpExtra is null) - { - errors.Add(string.Format(ResUI.InvalidProperty, "XHTTP Extra")); - } - } - - return errors; - } - - private async Task> ValidateGroupNode(ProfileItem item, ECoreType? coreType = null) - { - var errors = new List(); - - var hasCycle = await GroupProfileManager.HasCycle(item.IndexId, item.GetProtocolExtra()); - if (hasCycle) - { - errors.Add(string.Format(ResUI.GroupSelfReference, item.Remarks)); - return errors; - } - - var (childItems, _) = await GroupProfileManager.GetChildProfileItems(item); - - foreach (var childItem in childItems) - { - var childErrors = new List(); - - if (childItem is null) - { - childErrors.Add(string.Format(ResUI.NodeTagNotExist, "")); - continue; - } - - if (childItem.ConfigType is EConfigType.Custom or EConfigType.ProxyChain) - { - childErrors.Add(string.Format(ResUI.InvalidProperty, childItem.Remarks)); - continue; - } - - childErrors.AddRange(await ValidateNodeAndCoreSupport(childItem, coreType)); - errors.AddRange(childErrors.Select(s => s.Insert(0, $"{childItem.Remarks}: "))); - } - return errors; - } - - private static string? ValidateSingboxTransport(EConfigType configType, string net) - { - // sing-box does not support xhttp / kcp transports - if (SingboxUnsupportedTransports.Contains(net)) - { - return string.Format(ResUI.CoreNotSupportNetwork, nameof(ECoreType.sing_box), net); - } - - // sing-box does not support non-tcp transports for protocols other than vmess/trojan/vless/shadowsocks - if (!SingboxTransportSupportedProtocols.Contains(configType) && net != nameof(ETransport.tcp)) - { - return string.Format(ResUI.CoreNotSupportProtocolTransport, - nameof(ECoreType.sing_box), configType.ToString(), net); - } - - // sing-box shadowsocks only supports tcp/ws/quic transports - if (configType == EConfigType.Shadowsocks && !SingboxShadowsocksAllowedTransports.Contains(net)) - { - return string.Format(ResUI.CoreNotSupportProtocolTransport, - nameof(ECoreType.sing_box), configType.ToString(), net); - } - - return null; - } - - private async Task> ValidateRelatedNodesExistAndValid(ProfileItem? item) - { - var errors = new List(); - errors.AddRange(await ValidateProxyChainedNodeExistAndValid(item)); - errors.AddRange(await ValidateRoutingNodeExistAndValid(item)); - return errors; - } - - private async Task> ValidateProxyChainedNodeExistAndValid(ProfileItem? item) - { - var errors = new List(); - if (item is null) - { - return errors; - } - - // prev node and next node - var subItem = await AppManager.Instance.GetSubItem(item.Subid); - if (subItem is null) - { - return errors; - } - - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); - - await CollectProxyChainedNodeValidation(prevNode, subItem.PrevProfile, coreType, errors); - await CollectProxyChainedNodeValidation(nextNode, subItem.NextProfile, coreType, errors); - - return errors; - } - - private async Task CollectProxyChainedNodeValidation(ProfileItem? node, string tag, ECoreType coreType, List errors) - { - if (node is not null) - { - var nodeErrors = await ValidateNodeAndCoreSupport(node, coreType); - errors.AddRange(nodeErrors.Select(s => ResUI.ProxyChainedPrefix + $"{node.Remarks}: " + s)); - } - else if (tag.IsNotEmpty()) - { - errors.Add(ResUI.ProxyChainedPrefix + string.Format(ResUI.NodeTagNotExist, tag)); - } - } - - private async Task> ValidateRoutingNodeExistAndValid(ProfileItem? item) - { - var errors = new List(); - - if (item is null) - { - return errors; - } - - var coreType = AppManager.Instance.GetCoreType(item, item.ConfigType); - var routing = await ConfigHandler.GetDefaultRouting(AppManager.Instance.Config); - if (routing == null) - { - return errors; - } - - var rules = JsonUtils.Deserialize>(routing.RuleSet); - foreach (var ruleItem in rules ?? []) - { - if (!ruleItem.Enabled) - { - continue; - } - - var outboundTag = ruleItem.OutboundTag; - if (outboundTag.IsNullOrEmpty() || Global.OutboundTags.Contains(outboundTag)) - { - continue; - } - - var tagItem = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); - if (tagItem is null) - { - errors.Add(ResUI.RoutingRuleOutboundPrefix + string.Format(ResUI.NodeTagNotExist, outboundTag)); - continue; - } - - var tagErrors = await ValidateNodeAndCoreSupport(tagItem, coreType); - errors.AddRange(tagErrors.Select(s => ResUI.RoutingRuleOutboundPrefix + $"{tagItem.Remarks}: " + s)); - } - - return errors; - } -} diff --git a/v2rayN/ServiceLib/Manager/AppManager.cs b/v2rayN/ServiceLib/Manager/AppManager.cs index 0e471568..e773c2ef 100644 --- a/v2rayN/ServiceLib/Manager/AppManager.cs +++ b/v2rayN/ServiceLib/Manager/AppManager.cs @@ -230,6 +230,18 @@ public sealed class AppManager return await SQLiteHelper.Instance.TableAsync().FirstOrDefaultAsync(it => it.IndexId == indexId); } + public async Task> GetProfileItemsByIndexIds(IEnumerable indexIds) + { + var ids = indexIds.Where(id => !id.IsNullOrEmpty()).Distinct().ToList(); + if (ids.Count == 0) + { + return []; + } + return await SQLiteHelper.Instance.TableAsync() + .Where(it => ids.Contains(it.IndexId)) + .ToListAsync(); + } + public async Task GetProfileItemViaRemarks(string? remarks) { if (remarks.IsNullOrEmpty()) diff --git a/v2rayN/ServiceLib/Manager/CertPemManager.cs b/v2rayN/ServiceLib/Manager/CertPemManager.cs index bb8df227..b1c8d82a 100644 --- a/v2rayN/ServiceLib/Manager/CertPemManager.cs +++ b/v2rayN/ServiceLib/Manager/CertPemManager.cs @@ -215,7 +215,7 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { @@ -262,7 +262,7 @@ public class CertPemManager using var client = new TcpClient(); await client.ConnectAsync(domain, port > 0 ? port : 443, cts.Token); - using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); + await using var ssl = new SslStream(client.GetStream(), false, ValidateServerCertificate); var sslOptions = new SslClientAuthenticationOptions { @@ -280,11 +280,7 @@ public class CertPemManager var chain = new X509Chain(); chain.Build(certChain); - foreach (var element in chain.ChainElements) - { - var pem = ExportCertToPem(element.Certificate); - pemList.Add(pem); - } + pemList.AddRange(chain.ChainElements.Select(element => ExportCertToPem(element.Certificate))); return (pemList, null); } diff --git a/v2rayN/ServiceLib/Manager/CoreManager.cs b/v2rayN/ServiceLib/Manager/CoreManager.cs index 53240960..cd30450f 100644 --- a/v2rayN/ServiceLib/Manager/CoreManager.cs +++ b/v2rayN/ServiceLib/Manager/CoreManager.cs @@ -57,16 +57,27 @@ public class CoreManager } } - public async Task LoadCore(ProfileItem? node) + public async Task LoadCore(CoreConfigContext? context) { - if (node == null) + if (context == null) { await UpdateFunc(false, ResUI.CheckServerSettings); return; } + var contextMod = context; + var node = contextMod.Node; var fileName = Utils.GetBinConfigPath(Global.CoreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(node, fileName); + var preContext = ConfigHandler.GetPreSocksCoreConfigContext(contextMod); + if (preContext is not null) + { + contextMod = contextMod with + { + TunProtectSsPort = preContext.TunProtectSsPort, + ProxyRelaySsPort = preContext.ProxyRelaySsPort, + }; + } + var result = await CoreConfigHandler.GenerateClientConfig(contextMod, fileName); if (result.Success != true) { await UpdateFunc(true, result.Msg); @@ -85,8 +96,8 @@ public class CoreManager await WindowsUtils.RemoveTunDevice(); } - await CoreStart(node); - await CoreStartPreService(node); + await CoreStart(contextMod); + await CoreStartPreService(preContext); if (_processService != null) { await UpdateFunc(true, $"{node.GetSummary()}"); @@ -122,7 +133,8 @@ public class CoreManager var fileName = string.Format(Global.CoreSpeedtestConfigFileName, Utils.GetGuid(false)); var configPath = Utils.GetBinConfigPath(fileName); - var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, node, testItem, configPath); + var (context, _) = await CoreConfigContextBuilder.Build(_config, node); + var result = await CoreConfigHandler.GenerateClientSpeedtestConfig(_config, context, testItem, configPath); if (result.Success != true) { return null; @@ -165,8 +177,9 @@ public class CoreManager #region Private - private async Task CoreStart(ProfileItem node) + private async Task CoreStart(CoreConfigContext context) { + var node = context.Node; var coreType = AppManager.Instance.RunningCoreType = AppManager.Instance.GetCoreType(node, node.ConfigType); var coreInfo = CoreInfoManager.Instance.GetCoreInfo(coreType); @@ -179,27 +192,22 @@ public class CoreManager _processService = proc; } - private async Task CoreStartPreService(ProfileItem node) + private async Task CoreStartPreService(CoreConfigContext? preContext) { - if (_processService != null && !_processService.HasExited) + if (_processService is { HasExited: false } && preContext != null) { - var coreType = AppManager.Instance.GetCoreType(node, node.ConfigType); - var itemSocks = await ConfigHandler.GetPreSocksItem(_config, node, coreType); - if (itemSocks != null) + var preCoreType = preContext?.Node?.CoreType ?? ECoreType.sing_box; + var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); + var result = await CoreConfigHandler.GenerateClientConfig(preContext, fileName); + if (result.Success) { - var preCoreType = itemSocks.CoreType ?? ECoreType.sing_box; - var fileName = Utils.GetBinConfigPath(Global.CorePreConfigFileName); - var result = await CoreConfigHandler.GenerateClientConfig(itemSocks, fileName); - if (result.Success) + var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); + var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); + if (proc is null) { - var coreInfo = CoreInfoManager.Instance.GetCoreInfo(preCoreType); - var proc = await RunProcess(coreInfo, Global.CorePreConfigFileName, true, true); - if (proc is null) - { - return; - } - _processPreService = proc; + return; } + _processPreService = proc; } } } diff --git a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs index 53ef0429..510667dd 100644 --- a/v2rayN/ServiceLib/Manager/GroupProfileManager.cs +++ b/v2rayN/ServiceLib/Manager/GroupProfileManager.cs @@ -79,7 +79,7 @@ public class GroupProfileManager { if (protocolExtra == null) { - return new(); + return []; } var items = new List(); @@ -93,27 +93,44 @@ public class GroupProfileManager { if (extra == null || extra.ChildItems.IsNullOrEmpty()) { - return new(); + return []; } - var childProfiles = (await Task.WhenAll( - (Utils.String2List(extra.ChildItems) ?? new()) - .Where(p => !p.IsNullOrEmpty()) - .Select(AppManager.Instance.GetProfileItem) - )) - .Where(p => - p != null && - p.IsValid() && - p.ConfigType != EConfigType.Custom - ) - .ToList(); - return childProfiles ?? new(); + var childProfileIds = Utils.String2List(extra.ChildItems) + ?.Where(p => !string.IsNullOrEmpty(p)) + .ToList() ?? []; + if (childProfileIds.Count == 0) + { + return []; + } + + var childProfiles = await AppManager.Instance.GetProfileItemsByIndexIds(childProfileIds); + if (childProfiles == null || childProfiles.Count == 0) + { + return []; + } + + var profileMap = childProfiles + .Where(p => p != null && !p.IndexId.IsNullOrEmpty()) + .GroupBy(p => p!.IndexId!) + .ToDictionary(g => g.Key, g => g.First()); + + var ordered = new List(childProfileIds.Count); + foreach (var id in childProfileIds) + { + if (id != null && profileMap.TryGetValue(id, out var item) && item != null) + { + ordered.Add(item); + } + } + + return ordered; } private static async Task> GetSubChildProfileItems(ProtocolExtraItem? extra) { if (extra == null || extra.SubChildItems.IsNullOrEmpty()) { - return new(); + return []; } var childProfiles = await AppManager.Instance.ProfileItems(extra.SubChildItems ?? string.Empty); @@ -123,59 +140,31 @@ public class GroupProfileManager !p.ConfigType.IsComplexType() && (extra.Filter.IsNullOrEmpty() || Regex.IsMatch(p.Remarks, extra.Filter)) ) - .ToList() ?? new(); + .ToList() ?? []; } - public static async Task> GetAllChildDomainAddresses(ProfileItem profileItem) + public static async Task> GetAllChildProfileItems(ProfileItem profileItem) { - var childAddresses = new HashSet(); - var (childItems, _) = await GetChildProfileItems(profileItem); - foreach (var child in childItems) - { - if (!child.IsComplex()) - { - childAddresses.Add(child.Address); - } - else if (child.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(child); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } - } - } - return childAddresses; + var itemMap = new Dictionary(); + var visited = new HashSet(); + + await CollectChildItems(profileItem, itemMap, visited); + + return itemMap; } - public static async Task> GetAllChildEchQuerySni(ProfileItem profileItem) + private static async Task CollectChildItems(ProfileItem profileItem, Dictionary itemMap, + HashSet visited) { - var childAddresses = new HashSet(); var (childItems, _) = await GetChildProfileItems(profileItem); - foreach (var childNode in childItems) + foreach (var child in childItems.Where(child => visited.Add(child.IndexId))) { - if (!childNode.IsComplex() && !childNode.EchConfigList.IsNullOrEmpty()) + itemMap[child.IndexId] = child; + + if (child.ConfigType.IsGroupType()) { - if (childNode.StreamSecurity == Global.StreamSecurity - && childNode.EchConfigList?.Contains("://") == true) - { - var idx = childNode.EchConfigList.IndexOf('+'); - childAddresses.Add(idx > 0 ? childNode.EchConfigList[..idx] : childNode.Sni); - } - else - { - childAddresses.Add(childNode.Sni); - } - } - else if (childNode.ConfigType.IsGroupType()) - { - var subAddresses = await GetAllChildDomainAddresses(childNode); - foreach (var addr in subAddresses) - { - childAddresses.Add(addr); - } + await CollectChildItems(child, itemMap, visited); } } - return childAddresses; } } diff --git a/v2rayN/ServiceLib/Models/ConfigItems.cs b/v2rayN/ServiceLib/Models/ConfigItems.cs index 46caee85..7dc8c266 100644 --- a/v2rayN/ServiceLib/Models/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/ConfigItems.cs @@ -144,7 +144,6 @@ public class TunModeItem public bool StrictRoute { get; set; } = true; public string Stack { get; set; } public int Mtu { get; set; } - public bool EnableExInbound { get; set; } public bool EnableIPv6Address { get; set; } } diff --git a/v2rayN/ServiceLib/Models/CoreConfigContext.cs b/v2rayN/ServiceLib/Models/CoreConfigContext.cs new file mode 100644 index 00000000..a9a49f96 --- /dev/null +++ b/v2rayN/ServiceLib/Models/CoreConfigContext.cs @@ -0,0 +1,25 @@ +namespace ServiceLib.Models; + +public record CoreConfigContext +{ + public required ProfileItem Node { get; init; } + public required ECoreType RunCoreType { get; init; } + public RoutingItem? RoutingItem { get; init; } + public DNSItem? RawDnsItem { get; init; } + public SimpleDNSItem SimpleDnsItem { get; init; } = new(); + public Dictionary AllProxiesMap { get; init; } = new(); + public Config AppConfig { get; init; } = new(); + public FullConfigTemplateItem? FullConfigTemplate { get; init; } = new(); + + // Test ServerTestItem Map + public Dictionary ServerTestItemMap { get; init; } = new(); + + // TUN Compatibility + public bool IsTunEnabled { get; init; } = false; + public HashSet ProtectDomainList { get; init; } = new(); + // -> tun inbound --(if routing proxy)--> relay outbound + // -> proxy core (relay inbound --> proxy outbound --(dialerProxy)--> protect outbound) + // -> protect inbound -> direct proxy outbound data -> internet + public int TunProtectSsPort { get; init; } = 0; + public int ProxyRelaySsPort { get; init; } = 0; +} diff --git a/v2rayN/ServiceLib/Models/ProfileItem.cs b/v2rayN/ServiceLib/Models/ProfileItem.cs index 6e9d2a30..50ac8b90 100644 --- a/v2rayN/ServiceLib/Models/ProfileItem.cs +++ b/v2rayN/ServiceLib/Models/ProfileItem.cs @@ -178,6 +178,7 @@ public class ProfileItem public string CertSha { get; set; } public string EchConfigList { get; set; } public string EchForceQuery { get; set; } + public string Finalmask { get; set; } public string ProtoExtra { get; set; } diff --git a/v2rayN/ServiceLib/Models/SingboxConfig.cs b/v2rayN/ServiceLib/Models/SingboxConfig.cs index c0cd2e08..02deaabf 100644 --- a/v2rayN/ServiceLib/Models/SingboxConfig.cs +++ b/v2rayN/ServiceLib/Models/SingboxConfig.cs @@ -255,7 +255,7 @@ public class Server4Sbox : BaseServer4Sbox // public List? path { get; set; } // hosts public Dictionary>? predefined { get; set; } - // Deprecated + // Deprecated in sing-box 1.12.0 , kept for backward compatibility public string? address { get; set; } public string? address_resolver { get; set; } diff --git a/v2rayN/ServiceLib/Models/V2rayConfig.cs b/v2rayN/ServiceLib/Models/V2rayConfig.cs index 34456a8e..80baf43b 100644 --- a/v2rayN/ServiceLib/Models/V2rayConfig.cs +++ b/v2rayN/ServiceLib/Models/V2rayConfig.cs @@ -341,7 +341,7 @@ public class StreamSettings4Ray public HysteriaSettings4Ray? hysteriaSettings { get; set; } - public FinalMask4Ray? finalmask { get; set; } + public Finalmask4Ray? finalmask { get; set; } public Sockopt4Ray? sockopt { get; set; } } @@ -472,11 +472,11 @@ public class HysteriaSettings4Ray public class HysteriaUdpHop4Ray { - public string? ports { get; set; } + public string? port { get; set; } public string? interval { get; set; } } -public class FinalMask4Ray +public class Finalmask4Ray { public List? tcp { get; set; } public List? udp { get; set; } @@ -485,7 +485,7 @@ public class FinalMask4Ray public class Mask4Ray { public string type { get; set; } - public MaskSettings4Ray? settings { get; set; } + public object? settings { get; set; } } public class MaskSettings4Ray diff --git a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs index 6f223693..5e35d3bb 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.Designer.cs +++ b/v2rayN/ServiceLib/Resx/ResUI.Designer.cs @@ -132,33 +132,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Core '{0}' does not support network type '{1}'. 的本地化字符串。 - /// - public static string CoreNotSupportNetwork { - get { - return ResourceManager.GetString("CoreNotSupportNetwork", resourceCulture); - } - } - - /// - /// 查找类似 Core '{0}' does not support protocol '{1}'. 的本地化字符串。 - /// - public static string CoreNotSupportProtocol { - get { - return ResourceManager.GetString("CoreNotSupportProtocol", resourceCulture); - } - } - - /// - /// 查找类似 Core '{0}' does not support protocol '{1}' when using transport '{2}'. 的本地化字符串。 - /// - public static string CoreNotSupportProtocolTransport { - get { - return ResourceManager.GetString("CoreNotSupportProtocolTransport", resourceCulture); - } - } - /// /// 查找类似 Note that custom configuration relies entirely on your own configuration and does not work with all settings. If you want to use the system proxy, please modify the listening port manually. 的本地化字符串。 /// @@ -312,24 +285,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Group '{0}' is empty. Please add at least one node. 的本地化字符串。 - /// - public static string GroupEmpty { - get { - return ResourceManager.GetString("GroupEmpty", resourceCulture); - } - } - - /// - /// 查找类似 {0} Group cannot reference itself or have a circular reference 的本地化字符串。 - /// - public static string GroupSelfReference { - get { - return ResourceManager.GetString("GroupSelfReference", resourceCulture); - } - } - /// /// 查找类似 This is not the correct configuration, please check 的本地化字符串。 /// @@ -357,15 +312,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 The {0} property is invalid, please check. 的本地化字符串。 - /// - public static string InvalidProperty { - get { - return ResourceManager.GetString("InvalidProperty", resourceCulture); - } - } - /// /// 查找类似 Invalid address (URL) 的本地化字符串。 /// @@ -1914,6 +1860,33 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Core '{0}' does not support network type '{1}' 的本地化字符串。 + /// + public static string MsgCoreNotSupportNetwork { + get { + return ResourceManager.GetString("MsgCoreNotSupportNetwork", resourceCulture); + } + } + + /// + /// 查找类似 Core '{0}' does not support protocol '{1}' 的本地化字符串。 + /// + public static string MsgCoreNotSupportProtocol { + get { + return ResourceManager.GetString("MsgCoreNotSupportProtocol", resourceCulture); + } + } + + /// + /// 查找类似 Core '{0}' does not support protocol '{1}' when using transport '{2}' 的本地化字符串。 + /// + public static string MsgCoreNotSupportProtocolTransport { + get { + return ResourceManager.GetString("MsgCoreNotSupportProtocolTransport", resourceCulture); + } + } + /// /// 查找类似 Downloaded GeoFile: {0} successfully 的本地化字符串。 /// @@ -1959,6 +1932,60 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Group {0} child group node {1} error: {2}. Skipping this node. 的本地化字符串。 + /// + public static string MsgGroupChildGroupNodeError { + get { + return ResourceManager.GetString("MsgGroupChildGroupNodeError", resourceCulture); + } + } + + /// + /// 查找类似 Group {0} child group node {1} warning: {2} 的本地化字符串。 + /// + public static string MsgGroupChildGroupNodeWarning { + get { + return ResourceManager.GetString("MsgGroupChildGroupNodeWarning", resourceCulture); + } + } + + /// + /// 查找类似 Group {0} child node {1} error: {2}. Skipping this node. 的本地化字符串。 + /// + public static string MsgGroupChildNodeError { + get { + return ResourceManager.GetString("MsgGroupChildNodeError", resourceCulture); + } + } + + /// + /// 查找类似 Group {0} child node {1} warning: {2} 的本地化字符串。 + /// + public static string MsgGroupChildNodeWarning { + get { + return ResourceManager.GetString("MsgGroupChildNodeWarning", resourceCulture); + } + } + + /// + /// 查找类似 Group {0} has a cycle dependency on child node {1}. Skipping this node. 的本地化字符串。 + /// + public static string MsgGroupCycleDependency { + get { + return ResourceManager.GetString("MsgGroupCycleDependency", resourceCulture); + } + } + + /// + /// 查找类似 Group {0} has no valid child node. 的本地化字符串。 + /// + public static string MsgGroupNoValidChildNode { + get { + return ResourceManager.GetString("MsgGroupNoValidChildNode", resourceCulture); + } + } + /// /// 查找类似 Information 的本地化字符串。 /// @@ -1968,6 +1995,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 The {0} property is invalid, please check 的本地化字符串。 + /// + public static string MsgInvalidProperty { + get { + return ResourceManager.GetString("MsgInvalidProperty", resourceCulture); + } + } + /// /// 查找类似 Please enter the URL 的本地化字符串。 /// @@ -1977,6 +2013,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Not support protocol '{0}' 的本地化字符串。 + /// + public static string MsgNotSupportProtocol { + get { + return ResourceManager.GetString("MsgNotSupportProtocol", resourceCulture); + } + } + /// /// 查找类似 No valid subscriptions set 的本地化字符串。 /// @@ -1995,6 +2040,42 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Routing rule {0} has an empty outbound tag. Fallback to proxy node only. 的本地化字符串。 + /// + public static string MsgRoutingRuleEmptyOutboundTag { + get { + return ResourceManager.GetString("MsgRoutingRuleEmptyOutboundTag", resourceCulture); + } + } + + /// + /// 查找类似 Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. 的本地化字符串。 + /// + public static string MsgRoutingRuleOutboundNodeError { + get { + return ResourceManager.GetString("MsgRoutingRuleOutboundNodeError", resourceCulture); + } + } + + /// + /// 查找类似 Routing rule {0} outbound node {1} not found. Fallback to proxy node only. 的本地化字符串。 + /// + public static string MsgRoutingRuleOutboundNodeNotFound { + get { + return ResourceManager.GetString("MsgRoutingRuleOutboundNodeNotFound", resourceCulture); + } + } + + /// + /// 查找类似 Routing rule {0} outbound node {1} warning: {2} 的本地化字符串。 + /// + public static string MsgRoutingRuleOutboundNodeWarning { + get { + return ResourceManager.GetString("MsgRoutingRuleOutboundNodeWarning", resourceCulture); + } + } + /// /// 查找类似 Filter, press Enter to execute 的本地化字符串。 /// @@ -2049,6 +2130,24 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Subscription next proxy {0} not found. Skipping. 的本地化字符串。 + /// + public static string MsgSubscriptionNextProfileNotFound { + get { + return ResourceManager.GetString("MsgSubscriptionNextProfileNotFound", resourceCulture); + } + } + + /// + /// 查找类似 Subscription previous proxy {0} not found. Skipping. 的本地化字符串。 + /// + public static string MsgSubscriptionPrevProfileNotFound { + get { + return ResourceManager.GetString("MsgSubscriptionPrevProfileNotFound", resourceCulture); + } + } + /// /// 查找类似 Unpacking... 的本地化字符串。 /// @@ -2103,15 +2202,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Node alias '{0}' does not exist. 的本地化字符串。 - /// - public static string NodeTagNotExist { - get { - return ResourceManager.GetString("NodeTagNotExist", resourceCulture); - } - } - /// /// 查找类似 Non-VMess or SS protocol 的本地化字符串。 /// @@ -2139,15 +2229,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Not support protocol '{0}'. 的本地化字符串。 - /// - public static string NotSupportProtocol { - get { - return ResourceManager.GetString("NotSupportProtocol", resourceCulture); - } - } - /// /// 查找类似 Scan completed, no valid QR code found 的本地化字符串。 /// @@ -2229,24 +2310,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Policy group: 的本地化字符串。 - /// - public static string PolicyGroupPrefix { - get { - return ResourceManager.GetString("PolicyGroupPrefix", resourceCulture); - } - } - - /// - /// 查找类似 Proxy chained: 的本地化字符串。 - /// - public static string ProxyChainedPrefix { - get { - return ResourceManager.GetString("ProxyChainedPrefix", resourceCulture); - } - } - /// /// 查找类似 Global hotkey {0} registration failed, reason: {1} 的本地化字符串。 /// @@ -2310,15 +2373,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Routing rule outbound: 的本地化字符串。 - /// - public static string RoutingRuleOutboundPrefix { - get { - return ResourceManager.GetString("RoutingRuleOutboundPrefix", resourceCulture); - } - } - /// /// 查找类似 Run as Admin 的本地化字符串。 /// @@ -2916,6 +2970,15 @@ namespace ServiceLib.Resx { } } + /// + /// 查找类似 Finalmask 的本地化字符串。 + /// + public static string TbFinalmask { + get { + return ResourceManager.GetString("TbFinalmask", resourceCulture); + } + } + /// /// 查找类似 Fingerprint 的本地化字符串。 /// @@ -3771,15 +3834,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 Enable additional Inbound 的本地化字符串。 - /// - public static string TbSettingsEnableExInbound { - get { - return ResourceManager.GetString("TbSettingsEnableExInbound", resourceCulture); - } - } - /// /// 查找类似 Enable fragment 的本地化字符串。 /// @@ -3789,15 +3843,6 @@ namespace ServiceLib.Resx { } } - /// - /// 查找类似 which conflicts with the group previous proxy 的本地化字符串。 - /// - public static string TbSettingsEnableFragmentTips { - get { - return ResourceManager.GetString("TbSettingsEnableFragmentTips", resourceCulture); - } - } - /// /// 查找类似 Enable hardware acceleration (requires restart) 的本地化字符串。 /// diff --git a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx index dbce6ac4..0df75d77 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fa-Ir.resx @@ -1071,9 +1071,6 @@ MTU - - فعال سازی additional Inbound - فعال سازی آدرس IPv6 @@ -1113,9 +1110,6 @@ افزودن سرور [HTTP] - - which conflicts with the group previous proxy - فعال کردن فرگمنت @@ -1545,38 +1539,20 @@ Multi-Configuration Fallback by Xray - - Core '{0}' does not support network type '{1}'. + + Core '{0}' does not support network type '{1}' - - Core '{0}' does not support protocol '{1}' when using transport '{2}'. + + Core '{0}' does not support protocol '{1}' when using transport '{2}' - - Core '{0}' does not support protocol '{1}'. + + Core '{0}' does not support protocol '{1}' - - Proxy chained: + + The {0} property is invalid, please check - - Routing rule outbound: - - - Policy group: - - - Node alias '{0}' does not exist. - - - Group '{0}' is empty. Please add at least one node. - - - The {0} property is invalid, please check. - - - {0} Group cannot reference itself or have a circular reference - - - Not support protocol '{0}'. + + Not support protocol '{0}' If the system does not have a tray function, please do not enable it @@ -1671,4 +1647,43 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.fr.resx b/v2rayN/ServiceLib/Resx/ResUI.fr.resx index 005a51d5..6c882112 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.fr.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.fr.resx @@ -1068,9 +1068,6 @@ MTU - - Activer un port d’écoute supplémentaire - Activer IPv6 @@ -1110,9 +1107,6 @@ Ajouter [HTTP] - - En conflit avec le proxy amont de groupe - Activer le fragmentation (Fragment) @@ -1542,38 +1536,20 @@ Xray basculement (multi-sélection) - - Le cœur « {0} » ne prend pas en charge le type de réseau « {1} ». + + Le cœur « {0} » ne prend pas en charge le type de réseau « {1} » - - Le cœur « {0} » ne prend pas en charge le protocole « {1} » avec le mode de transport « {2} ». + + Le cœur « {0} » ne prend pas en charge le protocole « {1} » avec le mode de transport « {2} » - - Le cœur « {0} » ne prend pas en charge le protocole « {1} ». + + Le cœur « {0} » ne prend pas en charge le protocole « {1} » - - Chaîne de proxy : - - - Règle de routage sortante : - - - Groupe de stratégie : - - - L’alias « {0} » n’existe pas. - - - Le groupe « {0} » est vide. Veuillez ajouter au moins une configuration. - - + La propriété {0} est invalide, veuillez vérifier - - Le groupe {0} ne peut pas se référencer lui-même ni créer de référence circulaire - - - Protocole « {0} » non pris en charge. + + Protocole « {0} » non pris en charge Si le système n’a pas de zone de notif., n’activez pas cette option @@ -1639,33 +1615,72 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if EchForceQuery - Full certificate (chain), PEM format + Certificat complet (chaîne), format PEM - Certificate fingerprint (SHA-256) + Empreinte du certificat (SHA-256) - Serve Stale + Cache optimiste - Parallel Query + Requête parallèle - By default, invoked only during routing for resolution + Par défaut, utilisé uniquement lors du routage pour la résolution. - By default, invoked only during routing for resolution; ensure the remote server can reach this DNS + Par défaut, invoqué uniquement au routage pour la résolution. Vérifiez que le serveur distant peut joindre ce DNS. - If unset or "AsIs", DNS resolution uses the system DNS; otherwise, the internal DNS module is used. + Si non défini ou « AsIs », le DNS système est utilisé ; sinon, le module DNS interne est utilisé. - If unset or "AsIs", DNS resolution is performed by the remote server's DNS; otherwise, the internal DNS module is used. + Si non défini ou « AsIs », la résolution DNS est assurée par le serveur distant ; sinon, le module DNS interne est utilisé. - Port hopping interval + Intervalle de saut de port - Configuration item preview + Aperçu des sous-config - \ No newline at end of file + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + + diff --git a/v2rayN/ServiceLib/Resx/ResUI.hu.resx b/v2rayN/ServiceLib/Resx/ResUI.hu.resx index 3d169590..dd7d22f4 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.hu.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.hu.resx @@ -1071,9 +1071,6 @@ MTU - - További bejövő engedélyezése - IPv6 cím engedélyezése @@ -1113,9 +1110,6 @@ HTTP konfiguráció hozzáadása - - which conflicts with the group previous proxy - Fragment engedélyezése @@ -1545,38 +1539,20 @@ Multi-Configuration Fallback by Xray - - Core '{0}' does not support network type '{1}'. + + Core '{0}' does not support network type '{1}' - - Core '{0}' does not support protocol '{1}' when using transport '{2}'. + + Core '{0}' does not support protocol '{1}' when using transport '{2}' - - Core '{0}' does not support protocol '{1}'. + + Core '{0}' does not support protocol '{1}' - - Proxy chained: + + The {0} property is invalid, please check - - Routing rule outbound: - - - Policy group: - - - Node alias '{0}' does not exist. - - - Group '{0}' is empty. Please add at least one node. - - - The {0} property is invalid, please check. - - - {0} Group cannot reference itself or have a circular reference - - - Not support protocol '{0}'. + + Not support protocol '{0}' If the system does not have a tray function, please do not enable it @@ -1671,4 +1647,43 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.resx b/v2rayN/ServiceLib/Resx/ResUI.resx index 041a103b..fa53ccc8 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.resx @@ -1071,9 +1071,6 @@ MTU - - Enable additional Inbound - Enable IPv6 Address @@ -1113,9 +1110,6 @@ Add [HTTP] - - which conflicts with the group previous proxy - Enable fragment @@ -1545,38 +1539,20 @@ Fallback by Xray - - Core '{0}' does not support network type '{1}'. + + Core '{0}' does not support network type '{1}' - - Core '{0}' does not support protocol '{1}' when using transport '{2}'. + + Core '{0}' does not support protocol '{1}' when using transport '{2}' - - Core '{0}' does not support protocol '{1}'. + + Core '{0}' does not support protocol '{1}' - - Proxy chained: + + The {0} property is invalid, please check - - Routing rule outbound: - - - Policy group: - - - Node alias '{0}' does not exist. - - - Group '{0}' is empty. Please add at least one node. - - - The {0} property is invalid, please check. - - - {0} Group cannot reference itself or have a circular reference - - - Not support protocol '{0}'. + + Not support protocol '{0}' If the system does not have a tray function, please do not enable it @@ -1671,4 +1647,43 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.ru.resx b/v2rayN/ServiceLib/Resx/ResUI.ru.resx index 66ebef79..b33a10e3 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.ru.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.ru.resx @@ -1071,9 +1071,6 @@ MTU - - Включить дополнительный входящий канал - Включить IPv6 адреса @@ -1113,9 +1110,6 @@ Добавить сервер [HTTP] - - что конфликтует с предыдущим прокси группы - Включить фрагментацию (Fragment) @@ -1545,38 +1539,20 @@ Multi-Configuration Fallback by Xray - - Core '{0}' does not support network type '{1}'. + + Core '{0}' does not support network type '{1}' - - Core '{0}' does not support protocol '{1}' when using transport '{2}'. + + Core '{0}' does not support protocol '{1}' when using transport '{2}' - - Core '{0}' does not support protocol '{1}'. + + Core '{0}' does not support protocol '{1}' - - Proxy chained: + + The {0} property is invalid, please check - - Routing rule outbound: - - - Policy group: - - - Node alias '{0}' does not exist. - - - Group '{0}' is empty. Please add at least one node. - - - The {0} property is invalid, please check. - - - {0} Group cannot reference itself or have a circular reference - - - Not support protocol '{0}'. + + Not support protocol '{0}' If the system does not have a tray function, please do not enable it @@ -1671,4 +1647,43 @@ The "Get Certificate" action may fail if a self-signed certificate is used or if Configuration item preview + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx index c8936668..be76525e 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hans.resx @@ -1068,9 +1068,6 @@ MTU - - 启用额外监听端口 - 启用 IPv6 @@ -1110,9 +1107,6 @@ 添加 [HTTP] - - 和分组前置代理冲突 - 启用分片 (Fragment) @@ -1542,38 +1536,20 @@ 多选故障转移 Xray - - 核心 '{0}' 不支持网络类型 '{1}'。 + + 核心 '{0}' 不支持网络类型 '{1}' - - 核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}'。 + + 核心 '{0}' 在使用传输方式 '{2}' 时不支持协议 '{1}' - - 核心 '{0}' 不支持协议 '{1}'。 + + 核心 '{0}' 不支持协议 '{1}' - - 代理链: + + {0} 属性无效,请检查 - - 路由规则出站: - - - 策略组: - - - 别名 '{0}' 不存在。 - - - 组“{0}”为空。请至少添加一个配置。 - - - {0}属性无效,请检查 - - - {0} 分组不能引用自身或循环引用 - - - 不支持协议 '{0}'。 + + 不支持协议 '{0}' 如果系统没有托盘功能,请不要开启 @@ -1668,4 +1644,43 @@ 子配置项预览 + + Finalmask + + + 路由规则 {0} 出站节点 {1} 警告:{2} + + + 路由规则 {0} 出站节点 {1} 错误:{2}。已回退为仅使用代理节点。 + + + 节点组 {0} 与子节点 {1} 存在循环依赖,已跳过该节点。 + + + 节点组 {0} 子节点 {1} 警告:{2} + + + 节点组 {0} 子节点 {1} 错误:{2}。已跳过该节点。 + + + 节点组 {0} 子节点组 {1} 警告:{2} + + + 节点组 {0} 子节点组 {1} 错误:{2}。已跳过该节点。 + + + 节点组 {0} 下没有有效的子节点。 + + + 路由规则 {0} 的出站标签为空,已回退为仅使用代理节点。 + + + 路由规则 {0} 的出站节点 {1} 未找到,已回退为仅使用代理节点。 + + + 订阅前置节点 {0} 未找到,已跳过。 + + + 订阅后置节点 {0} 未找到,已跳过。 + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx index 2ee4dc4f..4132a27b 100644 --- a/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx +++ b/v2rayN/ServiceLib/Resx/ResUI.zh-Hant.resx @@ -1068,9 +1068,6 @@ MTU - - 啟用額外偵聽連接埠 - 啟用 IPv6 @@ -1110,9 +1107,6 @@ 新增 [HTTP] 節點 - - 和分組前置代理衝突 - 啟用分片(Fragment) @@ -1542,38 +1536,20 @@ 多選容錯移轉 Xray - - 核心 '{0}' 不支援網路類型 '{1}'. + + 核心 '{0}' 不支援網路類型 '{1}' - - 核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}'. + + 核心 '{0}' 在使用傳輸方式 '{2}' 時不支援協定 '{1}' - - 核心 '{0}' 不支援協定 '{1}'. + + 核心 '{0}' 不支援協定 '{1}' - - 代理鏈: + + {0} 屬性無效,請檢查 - - 路由規則出站: - - - 策略組: - - - 別名 '{0}' 不存在。 - - - 組“{0}”為空.請至少添加一個配置。 - - - {0}屬性無效,請檢查 - - - {0} 分組不能引用自身或循環引用 - - - 不支援協定 '{0}'. + + 不支援協定 '{0}' 如果系統沒有託盤功能,請不要開啟 @@ -1668,4 +1644,43 @@ Configuration item preview + + Finalmask + + + Routing rule {0} outbound node {1} warning: {2} + + + Routing rule {0} outbound node {1} error: {2}. Fallback to proxy node only. + + + Group {0} has a cycle dependency on child node {1}. Skipping this node. + + + Group {0} child node {1} warning: {2} + + + Group {0} child node {1} error: {2}. Skipping this node. + + + Group {0} child group node {1} warning: {2} + + + Group {0} child group node {1} error: {2}. Skipping this node. + + + Group {0} has no valid child node. + + + Routing rule {0} has an empty outbound tag. Fallback to proxy node only. + + + Routing rule {0} outbound node {1} not found. Fallback to proxy node only. + + + Subscription previous proxy {0} not found. Skipping. + + + Subscription next proxy {0} not found. Skipping. + \ No newline at end of file diff --git a/v2rayN/ServiceLib/Sample/SampleClientConfig b/v2rayN/ServiceLib/Sample/SampleClientConfig index 407a1307..b4ebf491 100644 --- a/v2rayN/ServiceLib/Sample/SampleClientConfig +++ b/v2rayN/ServiceLib/Sample/SampleClientConfig @@ -1,4 +1,4 @@ -{ +{ "log": { "access": "Vaccess.log", "error": "Verror.log", @@ -6,34 +6,6 @@ }, "inbounds": [], "outbounds": [ - { - "tag": "proxy", - "protocol": "vmess", - "settings": { - "vnext": [{ - "address": "", - "port": 0, - "users": [{ - "id": "", - "security": "auto" - }] - }], - "servers": [{ - "address": "", - "method": "", - "ota": false, - "password": "", - "port": 0, - "level": 1 - }] - }, - "streamSettings": { - "network": "tcp" - }, - "mux": { - "enabled": false - } - }, { "protocol": "freedom", "tag": "direct" diff --git a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig index b07fd72c..7ff36ec9 100644 --- a/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig +++ b/v2rayN/ServiceLib/Sample/SingboxSampleClientConfig @@ -5,19 +5,12 @@ }, "inbounds": [], "outbounds": [ - { - "type": "vless", - "tag": "proxy", - "server": "", - "server_port": 443 - }, { "type": "direct", "tag": "direct" } ], "route": { - "rules": [ - ] + "rules": [] } } \ No newline at end of file diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs index 397e9340..87b59e11 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/CoreConfigSingboxService.cs @@ -1,43 +1,34 @@ namespace ServiceLib.Services.CoreConfig; -public partial class CoreConfigSingboxService(Config config) +public partial class CoreConfigSingboxService(CoreConfigContext context) { - private readonly Config _config = config; private static readonly string _tag = "CoreConfigSingboxService"; + private readonly Config _config = context.AppConfig; + private readonly ProfileItem _node = context.Node; + + private SingboxConfig _coreConfig = new(); #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; - if (node.ConfigType.IsGroupType()) - { - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - return await GenerateClientMultipleLoadConfig(node); - - case EConfigType.ProxyChain: - return await GenerateClientChainConfig(node); - } - } - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); if (result.IsNullOrEmpty()) { @@ -45,44 +36,76 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(singboxConfig); + GenLog(); - await GenInbounds(singboxConfig); + GenInbounds(); - if (node.ConfigType == EConfigType.WireGuard) - { - singboxConfig.outbounds.RemoveAt(0); - var endpoints = new Endpoints4Sbox(); - await GenEndpoint(node, endpoints); - endpoints.tag = Global.ProxyTag; - singboxConfig.endpoints = new() { endpoints }; - } - else - { - await GenOutbound(node, singboxConfig.outbounds.First()); - } + GenOutbounds(); - await GenMoreOutbounds(node, singboxConfig); + GenRouting(); - await GenRouting(singboxConfig); + GenDns(); - await GenDns(node, singboxConfig); + GenExperimental(); - await GenExperimental(singboxConfig); - - await ConvertGeo2Ruleset(singboxConfig); + ConvertGeo2Ruleset(); ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = await ApplyFullConfigTemplate(singboxConfig); + ret.Data = ApplyFullConfigTemplate(); + if (context.TunProtectSsPort is > 0 and <= 65535) + { + var ssInbound = new + { + type = "shadowsocks", + tag = "tun-protect-ss", + listen = Global.Loopback, + listen_port = context.TunProtectSsPort, + method = "none", + password = "none", + }; + var directRule = new Rule4Sbox() + { + inbound = new List { ssInbound.tag }, + outbound = Global.DirectTag, + }; + var singboxConfigNode = JsonUtils.ParseJson(ret.Data.ToString())!.AsObject(); + var inboundsNode = singboxConfigNode["inbounds"]!.AsArray(); + inboundsNode.Add(JsonUtils.SerializeToNode(ssInbound, new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + })); + var routeNode = singboxConfigNode["route"]?.AsObject(); + var rulesNode = routeNode?["rules"]?.AsArray(); + var protectRuleNode = JsonUtils.SerializeToNode(directRule, + new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + if (rulesNode != null) + { + rulesNode.Insert(0, protectRuleNode); + } + else + { + var newRulesNode = new JsonArray() { protectRuleNode }; + if (routeNode is null) + { + var newRouteNode = new JsonObject() { ["rules"] = newRulesNode }; + singboxConfigNode["route"] = newRouteNode; + } + else + { + routeNode["rules"] = newRulesNode; + } + } + ret.Data = JsonUtils.Serialize(singboxConfigNode); + } return ret; } catch (Exception ex) @@ -93,17 +116,11 @@ public partial class CoreConfigSingboxService(Config config) } } - public async Task GenerateClientSpeedtestConfig(List selecteds) + public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); try { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); @@ -114,29 +131,19 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - List lstIpEndPoints = new(); - List lstTcpConns = new(); - try - { - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - await GenLog(singboxConfig); - //GenDns(new(), singboxConfig); - singboxConfig.inbounds.Clear(); - singboxConfig.outbounds.RemoveAt(0); + var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo(); + + GenLog(); + GenMinimizedDns(); + _coreConfig.inbounds.Clear(); + _coreConfig.outbounds.RemoveAt(0); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); @@ -150,8 +157,9 @@ public partial class CoreConfigSingboxService(Config config) { continue; } - var item = await AppManager.Instance.GetProfileItem(it.IndexId); - if (item is null || item.IsComplex() || !item.IsValid()) + var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId); + var item = context.AllProxiesMap.GetValueOrDefault(actIndexId); + if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid()) { continue; } @@ -190,26 +198,11 @@ public partial class CoreConfigSingboxService(Config config) type = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.type + inbound.listen_port.ToString(); - singboxConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); - //outbound - var server = await GenServer(item); - if (server is null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } var tag = Global.ProxyTag + inbound.listen_port.ToString(); - server.tag = tag; - if (server is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - singboxConfig.outbounds.Add(outbound); - } + var serverList = new CoreConfigSingboxService(context with { Node = item }).BuildAllProxyOutbounds(tag); + FillRangeProxy(serverList, _coreConfig, false); //rule Rule4Sbox rule = new() @@ -217,25 +210,11 @@ public partial class CoreConfigSingboxService(Config config) inbound = new List { inbound.tag }, outbound = tag }; - singboxConfig.route.rules.Add(rule); + _coreConfig.route.rules.Add(rule); } - var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (rawDNSItem != null && rawDNSItem.Enabled == true) - { - await GenDnsDomainsCompatible(singboxConfig, rawDNSItem); - } - else - { - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); - } - singboxConfig.route.default_domain_resolver = new() - { - server = Global.SingboxLocalDNSTag, - }; - ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) @@ -246,20 +225,20 @@ public partial class CoreConfigSingboxService(Config config) } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) + if (_node.GetNetwork() is nameof(ETransport.kcp) or nameof(ETransport.xhttp)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } @@ -272,44 +251,20 @@ public partial class CoreConfigSingboxService(Config config) return ret; } - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(singboxConfig); - if (node.ConfigType == EConfigType.WireGuard) - { - singboxConfig.outbounds.RemoveAt(0); - var endpoints = new Endpoints4Sbox(); - await GenEndpoint(node, endpoints); - endpoints.tag = Global.ProxyTag; - singboxConfig.endpoints = new() { endpoints }; - } - else - { - await GenOutbound(node, singboxConfig.outbounds.First()); - } - await GenMoreOutbounds(node, singboxConfig); - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (item != null && item.Enabled == true) - { - await GenDnsDomainsCompatible(singboxConfig, item); - } - else - { - await GenDnsDomains(singboxConfig, _config.SimpleDNSItem); - } - singboxConfig.route.default_domain_resolver = new() - { - server = Global.SingboxLocalDNSTag, - }; + GenLog(); + GenOutbounds(); + GenMinimizedDns(); - singboxConfig.route.rules.Clear(); - singboxConfig.inbounds.Clear(); - singboxConfig.inbounds.Add(new() + _coreConfig.route.rules.Clear(); + _coreConfig.inbounds.Clear(); + _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.mixed}{port}", listen = Global.Loopback, @@ -319,202 +274,7 @@ public partial class CoreConfigSingboxService(Config config) ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientMultipleLoadConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - singboxConfig.outbounds.RemoveAt(0); - - await GenLog(singboxConfig); - await GenInbounds(singboxConfig); - - var groupRet = await GenGroupOutbound(parentNode, singboxConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(singboxConfig); - await GenExperimental(singboxConfig); - await GenDns(parentNode, singboxConfig); - await ConvertGeo2Ruleset(singboxConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientChainConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.SingboxSampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var singboxConfig = JsonUtils.Deserialize(result); - if (singboxConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - singboxConfig.outbounds.RemoveAt(0); - - await GenLog(singboxConfig); - await GenInbounds(singboxConfig); - - var groupRet = await GenGroupOutbound(parentNode, singboxConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(singboxConfig); - await GenExperimental(singboxConfig); - await GenDns(parentNode, singboxConfig); - await ConvertGeo2Ruleset(singboxConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(singboxConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientCustomConfig(ProfileItem node, string? fileName) - { - var ret = new RetResult(); - if (node == null || fileName is null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - try - { - if (node == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - var addressFileName = node.Address; - if (addressFileName.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - if (!File.Exists(addressFileName)) - { - addressFileName = Path.Combine(Utils.GetConfigPath(), addressFileName); - } - if (!File.Exists(addressFileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "1"; - return ret; - } - - if (node.Address == Global.CoreMultipleLoadConfigFileName) - { - var txtFile = File.ReadAllText(addressFileName); - var singboxConfig = JsonUtils.Deserialize(txtFile); - if (singboxConfig == null) - { - File.Copy(addressFileName, fileName); - } - else - { - await GenInbounds(singboxConfig); - await GenExperimental(singboxConfig); - - var content = JsonUtils.Serialize(singboxConfig, true); - await File.WriteAllTextAsync(fileName, content); - } - } - else - { - File.Copy(addressFileName, fileName); - } - - //check again - if (!File.Exists(fileName)) - { - ret.Msg = ResUI.FailedReadConfiguration + "2"; - return ret; - } - - ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); - ret.Success = true; + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs index 6fe9b35a..25b29701 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxConfigTemplateService.cs @@ -2,29 +2,29 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task ApplyFullConfigTemplate(SingboxConfig singboxConfig) + private string ApplyFullConfigTemplate() { - var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.sing_box); - if (fullConfigTemplate == null || !fullConfigTemplate.Enabled) + var fullConfigTemplate = context.FullConfigTemplate; + if (fullConfigTemplate is not { Enabled: true }) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } - var fullConfigTemplateItem = _config.TunModeItem.EnableTun ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; + var fullConfigTemplateItem = context.IsTunEnabled ? fullConfigTemplate.TunConfig : fullConfigTemplate.Config; if (fullConfigTemplateItem.IsNullOrEmpty()) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplateItem); if (fullConfigTemplateNode == null) { - return JsonUtils.Serialize(singboxConfig); + return JsonUtils.Serialize(_coreConfig); } // Process outbounds - var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : new JsonArray(); - foreach (var outbound in singboxConfig.outbounds) + var customOutboundsNode = fullConfigTemplateNode["outbounds"] is JsonArray outbounds ? outbounds : []; + foreach (var outbound in _coreConfig.outbounds) { if (outbound.type.ToLower() is "direct" or "block") { @@ -42,10 +42,10 @@ public partial class CoreConfigSingboxService fullConfigTemplateNode["outbounds"] = customOutboundsNode; // Process endpoints - if (singboxConfig.endpoints != null && singboxConfig.endpoints.Count > 0) + if (_coreConfig.endpoints != null && _coreConfig.endpoints.Count > 0) { - var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : new JsonArray(); - foreach (var endpoint in singboxConfig.endpoints) + var customEndpointsNode = fullConfigTemplateNode["endpoints"] is JsonArray endpoints ? endpoints : []; + foreach (var endpoint in _coreConfig.endpoints) { if (endpoint.detour.IsNullOrEmpty() && !fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) { @@ -56,6 +56,6 @@ public partial class CoreConfigSingboxService fullConfigTemplateNode["endpoints"] = customEndpointsNode; } - return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); + return JsonUtils.Serialize(fullConfigTemplateNode); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs index 84df7538..03f4e80c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxDnsService.cs @@ -2,65 +2,68 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenDns(ProfileItem? node, SingboxConfig singboxConfig) + private void GenDns() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (item != null && item.Enabled == true) + var item = context.RawDnsItem; + if (item is { Enabled: true }) { - return await GenDnsCompatible(node, singboxConfig); + GenDnsCustom(); + return; } - var simpleDNSItem = _config.SimpleDNSItem; - await GenDnsServers(node, singboxConfig, simpleDNSItem); - await GenDnsRules(node, singboxConfig, simpleDNSItem); + GenDnsServers(); + GenDnsRules(); - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.independent_cache = true; + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.independent_cache = true; // final dns - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; var useDirectDns = false; if (routing != null) { var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; - useDirectDns = rules?.LastOrDefault() is { } lastRule && - lastRule.OutboundTag == Global.DirectTag && - (lastRule.Port == "0-65535" || - lastRule.Network == "tcp,udp" || - lastRule.Ip?.Contains("0.0.0.0/0") == true); + if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) + { + var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; + var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; + var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0"); + var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535"; + var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp"; + useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; + } } - singboxConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; - if ((!useDirectDns) && simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) + _coreConfig.dns.final = useDirectDns ? Global.SingboxDirectDNSTag : Global.SingboxRemoteDNSTag; + var simpleDnsItem = context.SimpleDnsItem; + if ((!useDirectDns) && simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { server = Global.SingboxFakeDNSTag, query_type = new List { 1, 28 }, // A and AAAA rewrite_ttl = 1, }); } - - await GenOutboundDnsRule(node, singboxConfig); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsServers(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private void GenDnsServers() { - var finalDns = await GenDnsDomains(singboxConfig, simpleDNSItem); + var simpleDnsItem = context.SimpleDnsItem; + var finalDns = GenBootstrapDns(); - var directDns = ParseDnsAddress(simpleDNSItem.DirectDNS); + var directDns = ParseDnsAddress(simpleDnsItem.DirectDNS ?? Global.DomainDirectDNSAddress.First()); directDns.tag = Global.SingboxDirectDNSTag; directDns.domain_resolver = Global.SingboxLocalDNSTag; - var remoteDns = ParseDnsAddress(simpleDNSItem.RemoteDNS); + var remoteDns = ParseDnsAddress(simpleDnsItem.RemoteDNS ?? Global.DomainRemoteDNSAddress.First()); remoteDns.tag = Global.SingboxRemoteDNSTag; remoteDns.detour = Global.ProxyTag; remoteDns.domain_resolver = Global.SingboxLocalDNSTag; @@ -71,12 +74,12 @@ public partial class CoreConfigSingboxService type = "hosts", predefined = new(), }; - if (simpleDNSItem.AddCommonHosts == true) + if (simpleDnsItem.AddCommonHosts == true) { hostsDns.predefined = Global.PredefinedHosts; } - if (simpleDNSItem.UseSystemHosts == true) + if (simpleDnsItem.UseSystemHosts == true) { var systemHosts = Utils.GetSystemHosts(); if (systemHosts != null && systemHosts.Count > 0) @@ -88,9 +91,9 @@ public partial class CoreConfigSingboxService } } - foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) + foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { - hostsDns.predefined[kvp.Key] = kvp.Value.Where(s => Utils.IsIpAddress(s)).ToList(); + hostsDns.predefined[kvp.Key] = kvp.Value.Where(Utils.IsIpAddress).ToList(); } foreach (var host in hostsDns.predefined) @@ -109,14 +112,14 @@ public partial class CoreConfigSingboxService } } - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.servers ??= []; - singboxConfig.dns.servers.Add(remoteDns); - singboxConfig.dns.servers.Add(directDns); - singboxConfig.dns.servers.Add(hostsDns); + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.servers ??= []; + _coreConfig.dns.servers.Add(remoteDns); + _coreConfig.dns.servers.Add(directDns); + _coreConfig.dns.servers.Add(hostsDns); // fake ip - if (simpleDNSItem.FakeIP == true) + if (simpleDnsItem.FakeIP == true) { var fakeip = new Server4Sbox { @@ -125,68 +128,50 @@ public partial class CoreConfigSingboxService inet4_range = "198.18.0.0/15", inet6_range = "fc00::/18", }; - singboxConfig.dns.servers.Add(fakeip); + _coreConfig.dns.servers.Add(fakeip); } - - // ech - var (_, dnsServer) = ParseEchParam(node?.EchConfigList); - if (dnsServer is not null) - { - dnsServer.tag = Global.SingboxEchDNSTag; - if (dnsServer.server is not null - && hostsDns.predefined.ContainsKey(dnsServer.server)) - { - dnsServer.domain_resolver = Global.SingboxHostsDNSTag; - } - else - { - dnsServer.domain_resolver = Global.SingboxLocalDNSTag; - } - singboxConfig.dns.servers.Add(dnsServer); - } - else if (node?.ConfigType.IsGroupType() == true) - { - var echDnsObject = JsonUtils.DeepCopy(directDns); - echDnsObject.tag = Global.SingboxEchDNSTag; - singboxConfig.dns.servers.Add(echDnsObject); - } - - return await Task.FromResult(0); } - private async Task GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) + private Server4Sbox GenBootstrapDns() { - var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS); + var finalDns = ParseDnsAddress(context.SimpleDnsItem?.BootstrapDNS ?? Global.DomainPureIPDNSAddress.First()); finalDns.tag = Global.SingboxLocalDNSTag; - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.servers ??= new List(); - singboxConfig.dns.servers.Add(finalDns); - return await Task.FromResult(finalDns); + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.servers ??= []; + _coreConfig.dns.servers.Add(finalDns); + return finalDns; } - private async Task GenDnsRules(ProfileItem? node, SingboxConfig singboxConfig, SimpleDNSItem simpleDNSItem) + private void GenDnsRules() { - singboxConfig.dns ??= new Dns4Sbox(); - singboxConfig.dns.rules ??= new List(); + var simpleDnsItem = context.SimpleDnsItem; + _coreConfig.dns ??= new Dns4Sbox(); + _coreConfig.dns.rules ??= []; - singboxConfig.dns.rules.AddRange(new[] + _coreConfig.dns.rules.AddRange(new[] { new Rule4Sbox { ip_accept_any = true, server = Global.SingboxHostsDNSTag }, new Rule4Sbox + { + server = Global.SingboxDirectDNSTag, + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), + domain = context.ProtectDomainList.ToList(), + }, + new Rule4Sbox { server = Global.SingboxRemoteDNSTag, - strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy), + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy), clash_mode = ERuleMode.Global.ToString() }, new Rule4Sbox { server = Global.SingboxDirectDNSTag, - strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom), + strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom), clash_mode = ERuleMode.Direct.ToString() } }); - foreach (var kvp in Utils.ParseHostsToDictionary(simpleDNSItem.Hosts)) + foreach (var kvp in Utils.ParseHostsToDictionary(simpleDnsItem.Hosts)) { var predefined = kvp.Value.First(); if (predefined.IsNullOrEmpty() || Utils.IsIpAddress(predefined)) @@ -197,7 +182,7 @@ public partial class CoreConfigSingboxService { // xray syntactic sugar for predefined // etc. #0 -> NOERROR - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { query_type = [1, 28], domain = [kvp.Key], @@ -225,38 +210,13 @@ public partial class CoreConfigSingboxService }; if (ParseV2Domain(kvp.Key, rule)) { - singboxConfig.dns.rules.Add(rule); + _coreConfig.dns.rules.Add(rule); } } - var (ech, _) = ParseEchParam(node?.EchConfigList); - if (ech is not null) + if (simpleDnsItem.BlockBindingQuery == true) { - var echDomain = ech.query_server_name ?? node?.Sni; - singboxConfig.dns.rules.Add(new() - { - query_type = [64, 65], - server = Global.SingboxEchDNSTag, - domain = echDomain is not null ? new List { echDomain } : null, - }); - } - else if (node?.ConfigType.IsGroupType() == true) - { - var queryServerNames = (await GroupProfileManager.GetAllChildEchQuerySni(node)).ToList(); - if (queryServerNames.Count > 0) - { - singboxConfig.dns.rules.Add(new() - { - query_type = [64, 65], - server = Global.SingboxEchDNSTag, - domain = queryServerNames, - }); - } - } - - if (simpleDNSItem.BlockBindingQuery == true) - { - singboxConfig.dns.rules.Add(new() + _coreConfig.dns.rules.Add(new() { query_type = [64, 65], action = "predefined", @@ -264,7 +224,7 @@ public partial class CoreConfigSingboxService }); } - if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == true) + if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == true) { var fakeipFilterRule = JsonUtils.Deserialize(EmbedUtils.GetEmbedText(Global.SingboxFakeIPFilterFileName)); fakeipFilterRule.invert = true; @@ -284,13 +244,13 @@ public partial class CoreConfigSingboxService ] }; - singboxConfig.dns.rules.Add(rule4Fake); + _coreConfig.dns.rules.Add(rule4Fake); } - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing == null) { - return 0; + return; } var rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; @@ -298,9 +258,9 @@ public partial class CoreConfigSingboxService var expectedIPsRegions = new List(); var regionNames = new HashSet(); - if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) + if (!string.IsNullOrEmpty(simpleDnsItem?.DirectExpectedIPs)) { - var ipItems = simpleDNSItem.DirectExpectedIPs + var ipItems = simpleDnsItem.DirectExpectedIPs .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) @@ -348,7 +308,7 @@ public partial class CoreConfigSingboxService if (item.OutboundTag == Global.DirectTag) { rule.server = Global.SingboxDirectDNSTag; - rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Freedom); + rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); if (expectedIPsRegions.Count > 0 && rule.geosite?.Count > 0) { @@ -373,31 +333,46 @@ public partial class CoreConfigSingboxService } else { - if (simpleDNSItem.FakeIP == true && simpleDNSItem.GlobalFakeIp == false) + if (simpleDnsItem.FakeIP == true && simpleDnsItem.GlobalFakeIp == false) { var rule4Fake = JsonUtils.DeepCopy(rule); rule4Fake.server = Global.SingboxFakeDNSTag; rule4Fake.query_type = new List { 1, 28 }; // A and AAAA rule4Fake.rewrite_ttl = 1; - singboxConfig.dns.rules.Add(rule4Fake); + _coreConfig.dns.rules.Add(rule4Fake); } rule.server = Global.SingboxRemoteDNSTag; - rule.strategy = Utils.DomainStrategy4Sbox(simpleDNSItem.Strategy4Proxy); + rule.strategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Proxy); } - singboxConfig.dns.rules.Add(rule); + _coreConfig.dns.rules.Add(rule); } - - return 0; } - private async Task GenDnsCompatible(ProfileItem? node, SingboxConfig singboxConfig) + private void GenMinimizedDns() + { + GenDnsServers(); + foreach (var server in _coreConfig.dns!.servers.Where(s => !string.IsNullOrEmpty(s.detour)).ToList()) + { + _coreConfig.dns.servers.Remove(server); + } + _coreConfig.dns ??= new(); + _coreConfig.dns.rules ??= []; + _coreConfig.dns.rules.Clear(); + _coreConfig.dns.final = Global.SingboxDirectDNSTag; + _coreConfig.route.default_domain_resolver = new() + { + server = Global.SingboxDirectDNSTag, + }; + } + + private void GenDnsCustom() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); + var item = context.RawDnsItem; var strDNS = string.Empty; - if (_config.TunModeItem.EnableTun) + if (context.IsTunEnabled) { strDNS = string.IsNullOrEmpty(item?.TunDNS) ? EmbedUtils.GetEmbedText(Global.TunSingboxDNSFileName) : item?.TunDNS; } @@ -409,61 +384,33 @@ public partial class CoreConfigSingboxService var dns4Sbox = JsonUtils.Deserialize(strDNS); if (dns4Sbox is null) { - return 0; + return; } - singboxConfig.dns = dns4Sbox; - - if (dns4Sbox.servers != null && dns4Sbox.servers.Count > 0 && dns4Sbox.servers.First().address.IsNullOrEmpty()) + _coreConfig.dns = dns4Sbox; + if (dns4Sbox.servers?.Count > 0 && + dns4Sbox.servers.First().address.IsNullOrEmpty()) { - await GenDnsDomainsCompatible(singboxConfig, item); + GenDnsProtectCustom(); } else { - await GenDnsDomainsLegacyCompatible(singboxConfig, item); + GenDnsProtectCustomLegacy(); } - - await GenOutboundDnsRule(node, singboxConfig); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsDomainsCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem) + private void GenDnsProtectCustom() { - var dns4Sbox = singboxConfig.dns ?? new(); + var dnsItem = context.RawDnsItem; + var dns4Sbox = _coreConfig.dns ?? new(); dns4Sbox.servers ??= []; dns4Sbox.rules ??= []; var tag = Global.SingboxLocalDNSTag; - - var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; - - var localDnsServer = ParseDnsAddress(finalDnsAddress); - localDnsServer.tag = tag; - - dns4Sbox.servers.Add(localDnsServer); - - singboxConfig.dns = dns4Sbox; - return await Task.FromResult(0); - } - - private async Task GenDnsDomainsLegacyCompatible(SingboxConfig singboxConfig, DNSItem? dnsItem) - { - var dns4Sbox = singboxConfig.dns ?? new(); - dns4Sbox.servers ??= []; - dns4Sbox.rules ??= []; - - var tag = Global.SingboxLocalDNSTag; - dns4Sbox.servers.Add(new() - { - tag = tag, - address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, - detour = Global.DirectTag, - strategy = string.IsNullOrEmpty(dnsItem?.DomainStrategy4Freedom) ? null : dnsItem?.DomainStrategy4Freedom, - }); dns4Sbox.rules.Insert(0, new() { server = tag, @@ -475,56 +422,41 @@ public partial class CoreConfigSingboxService clash_mode = ERuleMode.Global.ToString() }); - var lstDomain = singboxConfig.outbounds - .Where(t => t.server.IsNotEmpty() && Utils.IsDomain(t.server)) - .Select(t => t.server) - .Distinct() - .ToList(); - if (lstDomain != null && lstDomain.Count > 0) - { - dns4Sbox.rules.Insert(0, new() - { - server = tag, - domain = lstDomain - }); - } + var finalDnsAddress = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress; - singboxConfig.dns = dns4Sbox; - return await Task.FromResult(0); + var localDnsServer = ParseDnsAddress(finalDnsAddress); + localDnsServer.tag = tag; + + dns4Sbox.servers.Add(localDnsServer); + dns4Sbox.rules.Insert(0, BuildProtectDomainRule()); + + _coreConfig.dns = dns4Sbox; } - private async Task GenOutboundDnsRule(ProfileItem? node, SingboxConfig singboxConfig) + private void GenDnsProtectCustomLegacy() { - if (node == null) - { - return 0; - } + GenDnsProtectCustom(); - List domain = new(); - if (Utils.IsDomain(node.Address)) // normal outbound + _coreConfig.dns?.servers?.RemoveAll(s => s.tag == Global.SingboxLocalDNSTag); + var dnsItem = context.RawDnsItem; + var localDnsServer = new Server4Sbox() { - domain.Add(node.Address); - } - if (node.Address == Global.Loopback && node.SpiderX.IsNotEmpty()) // Tun2SocksAddress - { - domain.AddRange(Utils.String2List(node.SpiderX) - .Where(Utils.IsDomain) - .Distinct() - .ToList()); - } - if (domain.Count == 0) - { - return 0; - } + address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) + ? Global.DomainPureIPDNSAddress.FirstOrDefault() + : dnsItem?.DomainDNSAddress, + tag = Global.SingboxLocalDNSTag, + detour = Global.DirectTag, + }; + _coreConfig.dns?.servers?.Add(localDnsServer); + } - singboxConfig.dns.rules ??= new List(); - singboxConfig.dns.rules.Insert(0, new Rule4Sbox + private Rule4Sbox BuildProtectDomainRule() + { + return new() { server = Global.SingboxLocalDNSTag, - domain = domain, - }); - - return await Task.FromResult(0); + domain = context.ProtectDomainList.ToList(), + }; } private static Server4Sbox? ParseDnsAddress(string address) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs index 4e14c688..3a27c7da 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxInboundService.cs @@ -2,15 +2,16 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenInbounds(SingboxConfig singboxConfig) + private void GenInbounds() { try { var listen = "0.0.0.0"; - singboxConfig.inbounds = []; + var listenPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); + _coreConfig.inbounds = []; - if (!_config.TunModeItem.EnableTun - || (_config.TunModeItem.EnableTun && _config.TunModeItem.EnableExInbound && AppManager.Instance.RunningCoreType == ECoreType.sing_box)) + if (!context.IsTunEnabled + || (context.IsTunEnabled && _node.Port != listenPort)) { var inbound = new Inbound4Sbox() { @@ -18,23 +19,23 @@ public partial class CoreConfigSingboxService tag = EInboundProtocol.socks.ToString(), listen = Global.Loopback, }; - singboxConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); - inbound.listen_port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks); + inbound.listen_port = listenPort; if (_config.Inbound.First().SecondLocalPortEnabled) { - var inbound2 = GetInbound(inbound, EInboundProtocol.socks2, true); - singboxConfig.inbounds.Add(inbound2); + var inbound2 = BuildInbound(inbound, EInboundProtocol.socks2, true); + _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { - var inbound3 = GetInbound(inbound, EInboundProtocol.socks3, true); + var inbound3 = BuildInbound(inbound, EInboundProtocol.socks3, true); inbound3.listen = listen; - singboxConfig.inbounds.Add(inbound3); + _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) @@ -49,7 +50,7 @@ public partial class CoreConfigSingboxService } } - if (_config.TunModeItem.EnableTun) + if (context.IsTunEnabled) { if (_config.TunModeItem.Mtu <= 0) { @@ -71,17 +72,16 @@ public partial class CoreConfigSingboxService tunInbound.address = ["172.18.0.1/30"]; } - singboxConfig.inbounds.Add(tunInbound); + _coreConfig.inbounds.Add(tunInbound); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private Inbound4Sbox GetInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) + private Inbound4Sbox BuildInbound(Inbound4Sbox inItem, EInboundProtocol protocol, bool bSocks) { var inbound = JsonUtils.DeepCopy(inItem); inbound.tag = protocol.ToString(); diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs index 59e65471..b438242c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxLogService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenLog(SingboxConfig singboxConfig) + private void GenLog() { try { @@ -11,11 +11,11 @@ public partial class CoreConfigSingboxService case "debug": case "info": case "error": - singboxConfig.log.level = _config.CoreBasicItem.Loglevel; + _coreConfig.log.level = _config.CoreBasicItem.Loglevel; break; case "warning": - singboxConfig.log.level = "warn"; + _coreConfig.log.level = "warn"; break; default: @@ -23,18 +23,17 @@ public partial class CoreConfigSingboxService } if (_config.CoreBasicItem.Loglevel == Global.None) { - singboxConfig.log.disabled = true; + _coreConfig.log.disabled = true; } if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; - singboxConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.output = Utils.GetLogPath($"sbox_{dtNow:yyyy-MM-dd}.txt"); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs index 178350b3..b30c9c48 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxOutboundService.cs @@ -2,20 +2,97 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenOutbound(ProfileItem node, Outbound4Sbox outbound) + private void GenOutbounds() + { + var proxyOutbounds = BuildAllProxyOutbounds(); + FillRangeProxy(proxyOutbounds, _coreConfig, true); + } + + private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag, bool withSelector = true) + { + var proxyOutboundList = new List(); + if (!_node.ConfigType.IsComplexType()) + { + var outbound = BuildProxyOutbound(baseTagName); + proxyOutboundList.Add(outbound); + } + else + { + proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); + } + if (withSelector) + { + var proxyTags = proxyOutboundList.Where(n => n.tag.StartsWith(Global.ProxyTag)).Select(n => n.tag).ToList(); + if (proxyTags.Count > 1) + { + proxyOutboundList.InsertRange(0, BuildSelectorOutbounds(proxyTags, baseTagName)); + } + } + return proxyOutboundList; + } + + private BaseServer4Sbox BuildProxyOutbound(string baseTagName = Global.ProxyTag) + { + var outbound = BuildProxyServer(); + outbound.tag = baseTagName; + return outbound; + } + + private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + switch (_node.ConfigType) + { + case EConfigType.PolicyGroup: + proxyOutboundList = BuildOutboundsList(baseTagName); + break; + + case EConfigType.ProxyChain: + proxyOutboundList = BuildChainOutboundsList(baseTagName); + break; + } + return proxyOutboundList; + } + + private BaseServer4Sbox BuildProxyServer() { try { - var protocolExtra = node.GetProtocolExtra(); - outbound.server = node.Address; - outbound.server_port = node.Port; - outbound.type = Global.ProtocolTypes[node.ConfigType]; + var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); + if (_node.ConfigType == EConfigType.WireGuard) + { + var endpoint = JsonUtils.Deserialize(txtOutbound); + FillEndpoint(endpoint); + return endpoint; + } + else + { + var outbound = JsonUtils.Deserialize(txtOutbound); + FillOutbound(outbound); + return outbound; + } + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + } + throw new InvalidOperationException(); + } - switch (node.ConfigType) + private void FillOutbound(Outbound4Sbox outbound) + { + try + { + var protocolExtra = _node.GetProtocolExtra(); + outbound.server = _node.Address; + outbound.server_port = _node.Port; + outbound.type = Global.ProtocolTypes[_node.ConfigType]; + + switch (_node.ConfigType) { case EConfigType.VMess: { - outbound.uuid = node.Password; + outbound.uuid = _node.Password; outbound.alter_id = int.TryParse(protocolExtra.AlterId, out var result) ? result : 0; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) { @@ -26,41 +103,41 @@ public partial class CoreConfigSingboxService outbound.security = Global.DefaultSecurity; } - await GenOutboundMux(node, outbound); - await GenOutboundTransport(node, outbound); + FillOutboundMux(outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Shadowsocks: { - outbound.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + outbound.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : Global.None; - outbound.password = node.Password; + outbound.password = _node.Password; - if (node.Network == nameof(ETransport.tcp) && node.HeaderType == Global.TcpHeaderHttp) + if (_node.Network == nameof(ETransport.tcp) && _node.HeaderType == Global.TcpHeaderHttp) { outbound.plugin = "obfs-local"; - outbound.plugin_opts = $"obfs=http;obfs-host={node.RequestHost};"; + outbound.plugin_opts = $"obfs=http;obfs-host={_node.RequestHost};"; } else { var pluginArgs = string.Empty; - if (node.Network == nameof(ETransport.ws)) + if (_node.Network == nameof(ETransport.ws)) { pluginArgs += "mode=websocket;"; - pluginArgs += $"host={node.RequestHost};"; + pluginArgs += $"host={_node.RequestHost};"; // https://github.com/shadowsocks/v2ray-plugin/blob/e9af1cdd2549d528deb20a4ab8d61c5fbe51f306/args.go#L172 // Equal signs and commas [and backslashes] must be escaped with a backslash. - var path = node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); + var path = _node.Path.Replace("\\", "\\\\").Replace("=", "\\=").Replace(",", "\\,"); pluginArgs += $"path={path};"; } - else if (node.Network == nameof(ETransport.quic)) + else if (_node.Network == nameof(ETransport.quic)) { pluginArgs += "mode=quic;"; } - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { pluginArgs += "tls;"; - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var cert = certs.First(); @@ -84,33 +161,33 @@ public partial class CoreConfigSingboxService } } - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); break; } case EConfigType.SOCKS: { outbound.version = "5"; - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { - outbound.username = node.Username; - outbound.password = node.Password; + outbound.username = _node.Username; + outbound.password = _node.Password; } break; } case EConfigType.HTTP: { - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { - outbound.username = node.Username; - outbound.password = node.Password; + outbound.username = _node.Username; + outbound.password = _node.Password; } break; } case EConfigType.VLESS: { - outbound.uuid = node.Password; + outbound.uuid = _node.Password; outbound.packet_encoding = "xudp"; @@ -120,23 +197,23 @@ public partial class CoreConfigSingboxService } else { - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); } - await GenOutboundTransport(node, outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Trojan: { - outbound.password = node.Password; + outbound.password = _node.Password; - await GenOutboundMux(node, outbound); - await GenOutboundTransport(node, outbound); + FillOutboundMux(outbound); + FillOutboundTransport(outbound); break; } case EConfigType.Hysteria2: { - outbound.password = node.Password; + outbound.password = _node.Password; if (!protocolExtra.SalamanderPass.IsNullOrEmpty()) { @@ -190,37 +267,36 @@ public partial class CoreConfigSingboxService } case EConfigType.TUIC: { - outbound.uuid = node.Username; - outbound.password = node.Password; - outbound.congestion_control = node.HeaderType; + outbound.uuid = _node.Username; + outbound.password = _node.Password; + outbound.congestion_control = _node.HeaderType; break; } case EConfigType.Anytls: { - outbound.password = node.Password; + outbound.password = _node.Password; break; } } - await GenOutboundTls(node, outbound); + FillOutboundTls(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenEndpoint(ProfileItem node, Endpoints4Sbox endpoint) + private void FillEndpoint(Endpoints4Sbox endpoint) { try { - var protocolExtra = node.GetProtocolExtra(); + var protocolExtra = _node.GetProtocolExtra(); endpoint.address = Utils.String2List(protocolExtra.WgInterfaceAddress); - endpoint.type = Global.ProtocolTypes[node.ConfigType]; + endpoint.type = Global.ProtocolTypes[_node.ConfigType]; - switch (node.ConfigType) + switch (_node.ConfigType) { case EConfigType.WireGuard: { @@ -229,12 +305,12 @@ public partial class CoreConfigSingboxService public_key = protocolExtra.WgPublicKey, pre_shared_key = protocolExtra.WgPresharedKey, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), - address = node.Address, - port = node.Port, + address = _node.Address, + port = _node.Port, // TODO default ["0.0.0.0/0", "::/0"] allowed_ips = new() { "0.0.0.0/0", "::/0" }, }; - endpoint.private_key = node.Password; + endpoint.private_key = _node.Password; endpoint.mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(); endpoint.peers = [peer]; break; @@ -245,47 +321,13 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenServer(ProfileItem node) + private void FillOutboundMux(Outbound4Sbox outbound) { try { - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (node.ConfigType == EConfigType.WireGuard) - { - var endpoint = JsonUtils.Deserialize(txtOutbound); - var ret = await GenEndpoint(node, endpoint); - if (ret != 0) - { - return null; - } - return endpoint; - } - else - { - var outbound = JsonUtils.Deserialize(txtOutbound); - var ret = await GenOutbound(node, outbound); - if (ret != 0) - { - return null; - } - return outbound; - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return await Task.FromResult(null); - } - - private async Task GenOutboundMux(ProfileItem node, Outbound4Sbox outbound) - { - try - { - var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; + var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; if (muxEnabled && _config.Mux4SboxItem.Protocol.IsNotEmpty()) { var mux = new Multiplex4Sbox() @@ -302,66 +344,65 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenOutboundTls(ProfileItem node, Outbound4Sbox outbound) + private void FillOutboundTls(Outbound4Sbox outbound) { try { - if (node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity)) + if (_node.StreamSecurity is not (Global.StreamSecurityReality or Global.StreamSecurity)) { - return await Task.FromResult(0); + return; } - if (node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard) + if (_node.ConfigType is EConfigType.Shadowsocks or EConfigType.SOCKS or EConfigType.WireGuard) { - return await Task.FromResult(0); + return; } - var server_name = string.Empty; - if (node.Sni.IsNotEmpty()) + var serverName = string.Empty; + if (_node.Sni.IsNotEmpty()) { - server_name = node.Sni; + serverName = _node.Sni; } - else if (node.RequestHost.IsNotEmpty()) + else if (_node.RequestHost.IsNotEmpty()) { - server_name = Utils.String2List(node.RequestHost)?.First(); + serverName = Utils.String2List(_node.RequestHost)?.First(); } var tls = new Tls4Sbox() { enabled = true, record_fragment = _config.CoreBasicItem.EnableFragment ? true : null, - server_name = server_name, - insecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), + server_name = serverName, + insecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), + alpn = _node.GetAlpn(), }; - if (node.Fingerprint.IsNotEmpty()) + if (_node.Fingerprint.IsNotEmpty()) { tls.utls = new Utls4Sbox() { enabled = true, - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint }; } - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { tls.certificate = certs; tls.insecure = false; } } - else if (node.StreamSecurity == Global.StreamSecurityReality) + else if (_node.StreamSecurity == Global.StreamSecurityReality) { tls.reality = new Reality4Sbox() { enabled = true, - public_key = node.PublicKey, - short_id = node.ShortId + public_key = _node.PublicKey, + short_id = _node.ShortId }; tls.insecure = false; } - var (ech, _) = ParseEchParam(node.EchConfigList); + var (ech, _) = ParseEchParam(_node.EchConfigList); if (ech is not null) { tls.ech = ech; @@ -372,35 +413,34 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenOutboundTransport(ProfileItem node, Outbound4Sbox outbound) + private void FillOutboundTransport(Outbound4Sbox outbound) { try { var transport = new Transport4Sbox(); - switch (node.GetNetwork()) + switch (_node.GetNetwork()) { case nameof(ETransport.h2): transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); + transport.path = _node.Path.NullIfEmpty(); break; case nameof(ETransport.tcp): //http - if (node.HeaderType == Global.TcpHeaderHttp) + if (_node.HeaderType == Global.TcpHeaderHttp) { transport.type = nameof(ETransport.http); - transport.host = node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(node.RequestHost); - transport.path = node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.IsNullOrEmpty() ? null : Utils.String2List(_node.RequestHost); + transport.path = _node.Path.NullIfEmpty(); } break; case nameof(ETransport.ws): transport.type = nameof(ETransport.ws); - var wsPath = node.Path; + var wsPath = _node.Path; // Parse eh and ed parameters from path using regex if (!wsPath.IsNullOrEmpty()) @@ -429,19 +469,19 @@ public partial class CoreConfigSingboxService } transport.path = wsPath.NullIfEmpty(); - if (node.RequestHost.IsNotEmpty()) + if (_node.RequestHost.IsNotEmpty()) { transport.headers = new() { - Host = node.RequestHost + Host = _node.RequestHost }; } break; case nameof(ETransport.httpupgrade): transport.type = nameof(ETransport.httpupgrade); - transport.path = node.Path.NullIfEmpty(); - transport.host = node.RequestHost.NullIfEmpty(); + transport.path = _node.Path.NullIfEmpty(); + transport.host = _node.RequestHost.NullIfEmpty(); break; @@ -451,7 +491,7 @@ public partial class CoreConfigSingboxService case nameof(ETransport.grpc): transport.type = nameof(ETransport.grpc); - transport.service_name = node.Path; + transport.service_name = _node.Path; transport.idle_timeout = _config.GrpcItem.IdleTimeout?.ToString("##s"); transport.ping_timeout = _config.GrpcItem.HealthCheckTimeout?.ToString("##s"); transport.permit_without_stream = _config.GrpcItem.PermitWithoutStream; @@ -469,458 +509,190 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenGroupOutbound(ProfileItem node, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false) + private List BuildSelectorOutbounds(List proxyTags, string baseTagName = Global.ProxyTag) { - try + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + var outUrltest = new Outbound4Sbox { - if (!node.ConfigType.IsGroupType()) - { - return -1; - } - var hasCycle = await GroupProfileManager.HasCycle(node); - if (hasCycle) - { - return -1; - } + type = "urltest", + tag = $"{baseTagName}-auto", + outbounds = proxyTags, + interrupt_exist_connections = false, + }; - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - return -1; - } - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; - if (ignoreOriginChain) - { - await GenOutboundsList(childProfiles, singboxConfig, multipleLoad, baseTagName); - } - else - { - await GenOutboundsListWithChain(childProfiles, singboxConfig, multipleLoad, baseTagName); - } - - break; - - case EConfigType.ProxyChain: - await GenChainOutboundsList(childProfiles, singboxConfig, baseTagName); - break; - - default: - break; - } - } - catch (Exception ex) + if (multipleLoad == EMultipleLoad.Fallback) { - Logging.SaveLog(_tag, ex); + outUrltest.tolerance = 5000; } - return await Task.FromResult(0); + + // Add selector outbound (manual selection) + var outSelector = new Outbound4Sbox + { + type = "selector", + tag = baseTagName, + outbounds = JsonUtils.DeepCopy(proxyTags), + interrupt_exist_connections = false, + }; + outSelector.outbounds.Insert(0, outUrltest.tag); + + return [outSelector, outUrltest]; } - private async Task GenMoreOutbounds(ProfileItem node, SingboxConfig singboxConfig) + private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { - if (node.Subid.IsNullOrEmpty()) + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { - return 0; - } - try - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is null) + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { - return 0; - } - - //current proxy - BaseServer4Sbox? outbound = singboxConfig.endpoints?.FirstOrDefault(t => t.tag == Global.ProxyTag, null); - outbound ??= singboxConfig.outbounds.First(); - - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - - //Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - string? prevOutboundTag = null; - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) - { - prevOutboundTag = $"prev-{Global.ProxyTag}"; - var prevServer = await GenServer(prevNode); - prevServer.tag = prevOutboundTag; - if (prevServer is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (prevServer is Outbound4Sbox outboundPrev) - { - singboxConfig.outbounds.Add(outboundPrev); - } - } - var nextServer = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - - if (nextServer is not null) - { - if (nextServer is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Insert(0, endpoint); - } - else if (nextServer is Outbound4Sbox outboundNext) - { - singboxConfig.outbounds.Insert(0, outboundNext); - } + nodes.Add(node); } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenOutboundsListWithChain(List nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) - { - try - { - // Get outbound template and initialize lists - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - return 0; - } - - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints - var prevOutbounds = new List(); // Separate list for prev outbounds - var prevEndpoints = new List(); // Separate list for prev endpoints - var proxyTags = new List(); // For selector and urltest outbounds - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag - var prevIndex = 0; // Index for prev outbounds - - // Process each node - var index = 0; - foreach (var node in nodes) - { - index++; - - if (node.ConfigType.IsGroupType()) - { - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{index}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, singboxConfig, - profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - if (ret == 0) - { - proxyTags.Add(childBaseTagName); - } - continue; - } - - // Handle proxy chain - string? prevTag = null; - var currentServer = await GenServer(node); - var nextServer = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextServer != null) - { - nextServer = JsonUtils.DeepCopy(nextServer); - } - - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - - // current proxy - currentServer.tag = $"{baseTagName}-{index}"; - proxyTags.Add(currentServer.tag); - - if (!node.Subid.IsNullOrEmpty()) - { - if (prevProxyTags.TryGetValue(node.Subid, out var value)) - { - prevTag = value; // maybe null - } - else - { - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevTag = $"prev-{baseTagName}-{++prevIndex}"; - prevOutbound.tag = prevTag; - prevOutbounds.Add(prevOutbound); - } - prevProxyTags[node.Subid] = prevTag; - } - - nextServer = await GenChainOutbounds(subItem, currentServer, prevTag, nextServer); - if (!nextProxyCache.ContainsKey(node.Subid)) - { - nextProxyCache[node.Subid] = nextServer; - } - } - - if (nextServer is not null) - { - if (nextServer is Endpoints4Sbox nextEndpoint) - { - resultEndpoints.Add(nextEndpoint); - } - else if (nextServer is Outbound4Sbox nextOutbound) - { - resultOutbounds.Add(nextOutbound); - } - } - if (currentServer is Endpoints4Sbox currentEndpoint) - { - resultEndpoints.Add(currentEndpoint); - } - else if (currentServer is Outbound4Sbox currentOutbound) - { - resultOutbounds.Add(currentOutbound); - } - } - - // Add urltest outbound (auto selection based on latency) - if (proxyTags.Count > 0) - { - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{baseTagName}-auto", - outbounds = proxyTags, - interrupt_exist_connections = false, - }; - - if (multipleLoad == EMultipleLoad.Fallback) - { - outUrltest.tolerance = 5000; - } - - // Add selector outbound (manual selection) - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = baseTagName, - outbounds = JsonUtils.DeepCopy(proxyTags), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - - // Insert these at the beginning - resultOutbounds.Insert(0, outUrltest); - resultOutbounds.Insert(0, outSelector); - } - - // Merge results: first the selector/urltest/proxies, then other outbounds, and finally prev outbounds - var serverList = new List(); - serverList = serverList.Concat(prevOutbounds) - .Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenChainOutbounds(SubItem subItem, BaseServer4Sbox outbound, string? prevOutboundTag, BaseServer4Sbox? nextOutbound = null) - { - try - { - var txtOutbound = EmbedUtils.GetEmbedText(Global.SingboxSampleOutbound); - - if (!prevOutboundTag.IsNullOrEmpty()) - { - outbound.detour = prevOutboundTag; - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType)) - { - nextOutbound ??= await GenServer(nextNode); - nextOutbound.tag = outbound.tag; - - outbound.tag = $"mid-{outbound.tag}"; - nextOutbound.detour = outbound.tag; - } - return nextOutbound; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return null; - } - - private async Task GenOutboundsList(List nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) - { - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints - var proxyTags = new List(); // For selector and urltest outbounds + var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; - if (node == null) - { - continue; - } + var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{i + 1}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsList(childProfiles, singboxConfig, profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, singboxConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - if (ret == 0) - { - proxyTags.Add(childBaseTagName); - } + var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + resultOutbounds.AddRange(childProfiles); continue; } - var server = await GenServer(node); - if (server is null) - { - break; - } - server.tag = baseTagName + (i + 1).ToString(); - if (server is Endpoints4Sbox endpoint) - { - resultEndpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - resultOutbounds.Add(outbound); - } - proxyTags.Add(server.tag); + var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); + outbound.tag = currentTag; + resultOutbounds.Add(outbound); } - // Add urltest outbound (auto selection based on latency) - if (proxyTags.Count > 0) - { - var outUrltest = new Outbound4Sbox - { - type = "urltest", - tag = $"{baseTagName}-auto", - outbounds = proxyTags, - interrupt_exist_connections = false, - }; - if (multipleLoad == EMultipleLoad.Fallback) - { - outUrltest.tolerance = 5000; - } - // Add selector outbound (manual selection) - var outSelector = new Outbound4Sbox - { - type = "selector", - tag = baseTagName, - outbounds = JsonUtils.DeepCopy(proxyTags), - interrupt_exist_connections = false, - }; - outSelector.outbounds.Insert(0, outUrltest.tag); - // Insert these at the beginning - resultOutbounds.Insert(0, outUrltest); - resultOutbounds.Insert(0, outSelector); - } - var serverList = new List(); - serverList = serverList.Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - return await Task.FromResult(0); + return resultOutbounds; } - private async Task GenChainOutboundsList(List nodes, SingboxConfig singboxConfig, string baseTagName = Global.ProxyTag) + private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) + { + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) + { + nodes.Add(node); + } + } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); - var resultOutbounds = new List(); - var resultEndpoints = new List(); // For endpoints + var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; - var server = await GenServer(node); - - if (server is null) + var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; + var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; + if (node.ConfigType.IsGroupType()) { - break; + var childProfiles = new CoreConfigSingboxService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + if (!dialerProxyTag.IsNullOrEmpty()) + { + var chainEndNodes = + childProfiles.Where(n => n?.detour.IsNullOrEmpty() ?? true); + foreach (var chainEndNode in chainEndNodes) + { + chainEndNode.detour = dialerProxyTag; + } + } + if (i != 0) + { + var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); + if (chainStartNodes.Count == 1) + { + foreach (var existedChainEndNode in resultOutbounds.Where(n => n.detour == currentTag)) + { + existedChainEndNode.detour = chainStartNodes.First().tag; + } + } + else if (chainStartNodes.Count > 1) + { + var existedChainNodes = CloneOutbounds(resultOutbounds); + resultOutbounds.Clear(); + var j = 0; + foreach (var chainStartNode in chainStartNodes) + { + var existedChainNodesClone = CloneOutbounds(existedChainNodes); + foreach (var existedChainNode in existedChainNodesClone) + { + var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; + existedChainNode.tag = cloneTag; + } + for (var k = 0; k < existedChainNodesClone.Count; k++) + { + var existedChainNode = existedChainNodesClone[k]; + var previousDialerProxyTag = existedChainNode.detour; + var nextTag = k + 1 < existedChainNodesClone.Count + ? existedChainNodesClone[k + 1].tag + : chainStartNode.tag; + existedChainNode.detour = (previousDialerProxyTag == currentTag) + ? chainStartNode.tag + : nextTag; + resultOutbounds.Add(existedChainNode); + } + j++; + } + } + } + resultOutbounds.AddRange(childProfiles); + continue; + } + var outbound = new CoreConfigSingboxService(context with { Node = node, }).BuildProxyOutbound(); + + outbound.tag = currentTag; + + if (!dialerProxyTag.IsNullOrEmpty()) + { + outbound.detour = dialerProxyTag; } - if (i == 0) - { - server.tag = baseTagName; - } - else - { - server.tag = baseTagName + i.ToString(); - } - - if (i != nodesReverse.Count - 1) - { - server.detour = baseTagName + (i + 1).ToString(); - } - - if (server is Endpoints4Sbox endpoint) - { - resultEndpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - resultOutbounds.Add(outbound); - } + resultOutbounds.Add(outbound); } - var serverList = new List(); - serverList = serverList.Concat(resultOutbounds) - .Concat(resultEndpoints) - .ToList(); - await AddRangeOutbounds(serverList, singboxConfig, baseTagName == Global.ProxyTag); - return await Task.FromResult(0); + return resultOutbounds; } - private async Task AddRangeOutbounds(List servers, SingboxConfig singboxConfig, bool prepend = true) + private static List CloneOutbounds(List source) + { + if (source is null || source.Count == 0) + { + return []; + } + + var result = new List(source.Count); + foreach (var item in source) + { + BaseServer4Sbox? clone = null; + if (item is Outbound4Sbox outbound) + { + clone = JsonUtils.DeepCopy(outbound); + } + else if (item is Endpoints4Sbox endpoint) + { + clone = JsonUtils.DeepCopy(endpoint); + } + if (clone is not null) + { + result.Add(clone); + } + } + return result; + } + + private static void FillRangeProxy(List servers, SingboxConfig singboxConfig, bool prepend = true) { try { if (servers is null || servers.Count <= 0) { - return 0; + return; } var outbounds = servers.Where(s => s is Outbound4Sbox).Cast().ToList(); var endpoints = servers.Where(s => s is Endpoints4Sbox).Cast().ToList(); - singboxConfig.endpoints ??= new(); + singboxConfig.endpoints ??= []; if (prepend) { singboxConfig.outbounds.InsertRange(0, outbounds); @@ -936,10 +708,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) + private static (Ech4Sbox? ech, Server4Sbox? dnsServer) ParseEchParam(string? echConfig) { if (echConfig.IsNullOrEmpty()) { diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs index 6198d027..9bcb9adf 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRoutingService.cs @@ -2,23 +2,23 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenRouting(SingboxConfig singboxConfig) + private void GenRouting() { try { - singboxConfig.route.final = Global.ProxyTag; - var simpleDnsItem = _config.SimpleDNSItem; + _coreConfig.route.final = Global.ProxyTag; + var simpleDnsItem = context.SimpleDnsItem; var defaultDomainResolverTag = Global.SingboxDirectDNSTag; var directDnsStrategy = Utils.DomainStrategy4Sbox(simpleDnsItem.Strategy4Freedom); - var rawDNSItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); + var rawDNSItem = context.RawDnsItem; if (rawDNSItem is { Enabled: true }) { defaultDomainResolverTag = Global.SingboxLocalDNSTag; directDnsStrategy = rawDNSItem.DomainStrategy4Freedom.IsNullOrEmpty() ? null : rawDNSItem.DomainStrategy4Freedom; } - singboxConfig.route.default_domain_resolver = new() + _coreConfig.route.default_domain_resolver = new() { server = defaultDomainResolverTag, strategy = directDnsStrategy @@ -26,23 +26,23 @@ public partial class CoreConfigSingboxService if (_config.TunModeItem.EnableTun) { - singboxConfig.route.auto_detect_interface = true; + _coreConfig.route.auto_detect_interface = true; var tunRules = JsonUtils.Deserialize>(EmbedUtils.GetEmbedText(Global.TunSingboxRulesFileName)); if (tunRules != null) { - singboxConfig.route.rules.AddRange(tunRules); + _coreConfig.route.rules.AddRange(tunRules); } - GenRoutingDirectExe(out var lstDnsExe, out var lstDirectExe); - singboxConfig.route.rules.Add(new() + var (lstDnsExe, lstDirectExe) = BuildRoutingDirectExe(); + _coreConfig.route.rules.Add(new() { - port = new() { 53 }, + port = [53], action = "hijack-dns", process_name = lstDnsExe }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, process_name = lstDirectExe @@ -51,66 +51,62 @@ public partial class CoreConfigSingboxService if (_config.Inbound.First().SniffingEnabled) { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { action = "sniff" }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { - protocol = new() { "dns" }, + protocol = ["dns"], action = "hijack-dns" }); } else { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { - port = new() { 53 }, - network = new() { "udp" }, + port = [53], + network = ["udp"], action = "hijack-dns" }); } var hostsDomains = new List(); - var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box); - if (dnsItem == null || !dnsItem.Enabled) + if (rawDNSItem is not { Enabled: true }) { var userHostsMap = Utils.ParseHostsToDictionary(simpleDnsItem.Hosts); hostsDomains.AddRange(userHostsMap.Select(kvp => kvp.Key)); if (simpleDnsItem.UseSystemHosts == true) { var systemHostsMap = Utils.GetSystemHosts(); - foreach (var kvp in systemHostsMap) - { - hostsDomains.Add(kvp.Key); - } + hostsDomains.AddRange(systemHostsMap.Select(kvp => kvp.Key)); } } if (hostsDomains.Count > 0) { - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { action = "resolve", domain = hostsDomains, }); } - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.DirectTag, clash_mode = ERuleMode.Direct.ToString() }); - singboxConfig.route.rules.Add(new() + _coreConfig.route.rules.Add(new() { outbound = Global.ProxyTag, clash_mode = ERuleMode.Global.ToString() }); var domainStrategy = _config.RoutingBasicItem.DomainStrategy4Singbox.NullIfEmpty(); - var defaultRouting = await ConfigHandler.GetDefaultRouting(_config); - if (defaultRouting.DomainStrategy4Singbox.IsNotEmpty()) + var routing = context.RoutingItem; + if (routing.DomainStrategy4Singbox.IsNotEmpty()) { - domainStrategy = defaultRouting.DomainStrategy4Singbox; + domainStrategy = routing.DomainStrategy4Singbox; } var resolveRule = new Rule4Sbox { @@ -119,10 +115,9 @@ public partial class CoreConfigSingboxService }; if (_config.RoutingBasicItem.DomainStrategy == Global.IPOnDemand) { - singboxConfig.route.rules.Add(resolveRule); + _coreConfig.route.rules.Add(resolveRule); } - var routing = await ConfigHandler.GetDefaultRouting(_config); var ipRules = new List(); if (routing != null) { @@ -139,7 +134,7 @@ public partial class CoreConfigSingboxService continue; } - await GenRoutingUserRule(item1, singboxConfig); + GenRoutingUserRule(item1); if (item1.Ip?.Count > 0) { @@ -149,10 +144,10 @@ public partial class CoreConfigSingboxService } if (_config.RoutingBasicItem.DomainStrategy == Global.IPIfNonMatch) { - singboxConfig.route.rules.Add(resolveRule); + _coreConfig.route.rules.Add(resolveRule); foreach (var item2 in ipRules) { - await GenRoutingUserRule(item2, singboxConfig); + GenRoutingUserRule(item2); } } } @@ -160,10 +155,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return 0; } - private void GenRoutingDirectExe(out List lstDnsExe, out List lstDirectExe) + private static (List lstDnsExe, List lstDirectExe) BuildRoutingDirectExe() { var dnsExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); var directExeSet = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -187,20 +181,22 @@ public partial class CoreConfigSingboxService } } - lstDnsExe = new List(dnsExeSet); - lstDirectExe = new List(directExeSet); + var lstDnsExe = new List(dnsExeSet); + var lstDirectExe = new List(directExeSet); + + return (lstDnsExe, lstDirectExe); } - private async Task GenRoutingUserRule(RulesItem item, SingboxConfig singboxConfig) + private void GenRoutingUserRule(RulesItem? item) { try { if (item == null) { - return 0; + return; } - item.OutboundTag = await GenRoutingUserRuleOutbound(item.OutboundTag, singboxConfig); - var rules = singboxConfig.route.rules; + item.OutboundTag = GenRoutingUserRuleOutbound(item.OutboundTag ?? Global.ProxyTag); + var rules = _coreConfig.route.rules; var rule = new Rule4Sbox(); if (item.OutboundTag == "block") @@ -326,10 +322,9 @@ public partial class CoreConfigSingboxService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private bool ParseV2Domain(string domain, Rule4Sbox rule) + private static bool ParseV2Domain(string domain, Rule4Sbox rule) { if (domain.StartsWith('#') || domain.StartsWith("ext:") || domain.StartsWith("ext-domain:")) { @@ -368,7 +363,7 @@ public partial class CoreConfigSingboxService return true; } - private bool ParseV2Address(string address, Rule4Sbox rule) + private static bool ParseV2Address(string address, Rule4Sbox rule) { if (address.StartsWith("ext:") || address.StartsWith("ext-ip:")) { @@ -401,14 +396,14 @@ public partial class CoreConfigSingboxService return true; } - private async Task GenRoutingUserRuleOutbound(string outboundTag, SingboxConfig singboxConfig) + private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } - var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); + var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.SingboxSupportConfigType.Contains(node.ConfigType) @@ -418,39 +413,15 @@ public partial class CoreConfigSingboxService } var tag = $"{node.IndexId}-{Global.ProxyTag}"; - if (singboxConfig.outbounds.Any(o => o.tag == tag) - || (singboxConfig.endpoints != null && singboxConfig.endpoints.Any(e => e.tag == tag))) + if (_coreConfig.outbounds.Any(o => o.tag.StartsWith(tag)) + || (_coreConfig.endpoints != null && _coreConfig.endpoints.Any(e => e.tag.StartsWith(tag)))) { return tag; } - if (node.ConfigType.IsGroupType()) - { - var ret = await GenGroupOutbound(node, singboxConfig, tag); - if (ret == 0) - { - return tag; - } - return Global.ProxyTag; - } + var proxyOutbounds = new CoreConfigSingboxService(context with { Node = node, }).BuildAllProxyOutbounds(tag); + FillRangeProxy(proxyOutbounds, _coreConfig, false); - var server = await GenServer(node); - if (server is null) - { - return Global.ProxyTag; - } - - server.tag = tag; - if (server is Endpoints4Sbox endpoint) - { - singboxConfig.endpoints ??= new(); - singboxConfig.endpoints.Add(endpoint); - } - else if (server is Outbound4Sbox outbound) - { - singboxConfig.outbounds.Add(outbound); - } - - return server.tag; + return tag; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs index 7d26ca2f..bb4c4b3a 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxRulesetService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task ConvertGeo2Ruleset(SingboxConfig singboxConfig) + private void ConvertGeo2Ruleset() { static void AddRuleSets(List ruleSets, List? rule_set) { @@ -16,14 +16,14 @@ public partial class CoreConfigSingboxService var ruleSets = new List(); //convert route geosite & geoip to ruleset - foreach (var rule in singboxConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.route.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; AddRuleSets(ruleSets, rule.rule_set); } - foreach (var rule in singboxConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.route.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); @@ -32,24 +32,24 @@ public partial class CoreConfigSingboxService } //convert dns geosite & geoip to ruleset - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geosite?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geosite?.Select(t => $"{geosite}-{t}").ToList()); rule.geosite = null; } - foreach (var rule in singboxConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) + foreach (var rule in _coreConfig.dns?.rules.Where(t => t.geoip?.Count > 0).ToList() ?? []) { rule.rule_set ??= new List(); rule.rule_set.AddRange(rule?.geoip?.Select(t => $"{geoip}-{t}").ToList()); rule.geoip = null; } - foreach (var dnsRule in singboxConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) + foreach (var dnsRule in _coreConfig.dns?.rules.Where(t => t.rule_set?.Count > 0).ToList() ?? []) { AddRuleSets(ruleSets, dnsRule.rule_set); } //rules in rules - foreach (var item in singboxConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) + foreach (var item in _coreConfig.dns?.rules.Where(t => t.rules?.Count > 0).Select(t => t.rules).ToList() ?? []) { foreach (var item2 in item ?? []) { @@ -60,7 +60,7 @@ public partial class CoreConfigSingboxService //load custom ruleset file List customRulesets = []; - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing.CustomRulesetPath4Singbox.IsNotEmpty()) { var result = EmbedUtils.LoadResource(routing.CustomRulesetPath4Singbox); @@ -78,7 +78,7 @@ public partial class CoreConfigSingboxService var localSrss = Utils.GetBinPath("srss"); //Add ruleset srs - singboxConfig.route.rule_set = []; + _coreConfig.route.rule_set = []; foreach (var item in new HashSet(ruleSets)) { if (item.IsNullOrEmpty()) @@ -113,9 +113,7 @@ public partial class CoreConfigSingboxService }; } } - singboxConfig.route.rule_set.Add(customRuleset); + _coreConfig.route.rule_set.Add(customRuleset); } - - return 0; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs index c3acd810..e2beff34 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/Singbox/SingboxStatisticService.cs @@ -2,12 +2,12 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigSingboxService { - private async Task GenExperimental(SingboxConfig singboxConfig) + private void GenExperimental() { //if (_config.guiItem.enableStatistics) { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.clash_api = new Clash_Api4Sbox() + _coreConfig.experimental ??= new Experimental4Sbox(); + _coreConfig.experimental.clash_api = new Clash_Api4Sbox() { external_controller = $"{Global.Loopback}:{AppManager.Instance.StatePort2}", }; @@ -15,15 +15,13 @@ public partial class CoreConfigSingboxService if (_config.CoreBasicItem.EnableCacheFile4Sbox) { - singboxConfig.experimental ??= new Experimental4Sbox(); - singboxConfig.experimental.cache_file = new CacheFile4Sbox() + _coreConfig.experimental ??= new Experimental4Sbox(); + _coreConfig.experimental.cache_file = new CacheFile4Sbox() { enabled = true, path = Utils.GetBinPath("cache.db"), - store_fakeip = _config.SimpleDNSItem.FakeIP == true + store_fakeip = context.SimpleDnsItem.FakeIP == true }; } - - return await Task.FromResult(0); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs index d753fb3c..e34dceb6 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/CoreConfigV2rayService.cs @@ -1,44 +1,39 @@ namespace ServiceLib.Services.CoreConfig; -public partial class CoreConfigV2rayService(Config config) +public partial class CoreConfigV2rayService(CoreConfigContext context) { - private readonly Config _config = config; private static readonly string _tag = "CoreConfigV2rayService"; + private readonly Config _config = context.AppConfig; + private readonly ProfileItem _node = context.Node; + + private V2rayConfig _coreConfig = new(); #region public gen function - public async Task GenerateClientConfigContent(ProfileItem node) + public RetResult GenerateClientConfigContent() { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (context.IsTunEnabled && context.TunProtectSsPort > 0 && context.ProxyRelaySsPort > 0) + { + return GenerateClientProxyRelayConfig(); + } + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.quic)) + if (_node.GetNetwork() is nameof(ETransport.quic)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } ret.Msg = ResUI.InitialConfiguration; - if (node.ConfigType.IsGroupType()) - { - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - return await GenerateClientMultipleLoadConfig(node); - - case EConfigType.ProxyChain: - return await GenerateClientChainConfig(node); - } - } - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); if (result.IsNullOrEmpty()) { @@ -46,30 +41,34 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(v2rayConfig); + GenLog(); - await GenInbounds(v2rayConfig); + GenInbounds(); - await GenOutbound(node, v2rayConfig.outbounds.First()); + GenOutbounds(); - await GenMoreOutbounds(node, v2rayConfig); + GenRouting(); - await GenRouting(v2rayConfig); + GenDns(); - await GenDns(node, v2rayConfig); + GenStatistic(); - await GenStatistic(v2rayConfig); + var finalRule = BuildFinalRule(); + if (!string.IsNullOrEmpty(finalRule?.balancerTag)) + { + _coreConfig.routing.rules.Add(finalRule); + } ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); + ret.Data = ApplyFullConfigTemplate(); return ret; } catch (Exception ex) @@ -80,18 +79,11 @@ public partial class CoreConfigV2rayService(Config config) } } - public async Task GenerateClientMultipleLoadConfig(ProfileItem parentNode) + public RetResult GenerateClientSpeedtestConfig(List selecteds) { var ret = new RetResult(); - try { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - ret.Msg = ResUI.InitialConfiguration; var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); @@ -102,193 +94,19 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - v2rayConfig.outbounds.RemoveAt(0); - - await GenLog(v2rayConfig); - await GenInbounds(v2rayConfig); - - var groupRet = await GenGroupOutbound(parentNode, v2rayConfig); - if (groupRet != 0) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenRouting(v2rayConfig); - await GenDns(null, v2rayConfig); - await GenStatistic(v2rayConfig); + var (lstIpEndPoints, lstTcpConns) = Utils.GetActiveNetworkInfo(); - var defaultBalancerTag = $"{Global.ProxyTag}{Global.BalancerTagSuffix}"; - - //add rule - var rules = v2rayConfig.routing.rules; - if (rules?.Count > 0 && ((v2rayConfig.routing.balancers?.Count ?? 0) > 0)) - { - var balancerTagSet = v2rayConfig.routing.balancers - .Select(b => b.tag) - .ToHashSet(); - - foreach (var rule in rules) - { - if (rule.outboundTag == null) - { - continue; - } - - if (balancerTagSet.Contains(rule.outboundTag)) - { - rule.balancerTag = rule.outboundTag; - rule.outboundTag = null; - continue; - } - - var outboundWithSuffix = rule.outboundTag + Global.BalancerTagSuffix; - if (balancerTagSet.Contains(outboundWithSuffix)) - { - rule.balancerTag = outboundWithSuffix; - rule.outboundTag = null; - } - } - } - if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) - { - v2rayConfig.routing.rules.Add(new() - { - ip = ["0.0.0.0/0", "::/0"], - balancerTag = defaultBalancerTag, - type = "field" - }); - } - else - { - v2rayConfig.routing.rules.Add(new() - { - network = "tcp,udp", - balancerTag = defaultBalancerTag, - type = "field" - }); - } - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientChainConfig(ProfileItem parentNode) - { - var ret = new RetResult(); - - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - v2rayConfig.outbounds.RemoveAt(0); - - await GenLog(v2rayConfig); - await GenInbounds(v2rayConfig); - - var groupRet = await GenGroupOutbound(parentNode, v2rayConfig); - if (groupRet != 0) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - - await GenRouting(v2rayConfig); - await GenDns(null, v2rayConfig); - await GenStatistic(v2rayConfig); - - ret.Success = true; - - ret.Data = await ApplyFullConfigTemplate(v2rayConfig); - return ret; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - } - - public async Task GenerateClientSpeedtestConfig(List selecteds) - { - var ret = new RetResult(); - try - { - if (_config == null) - { - ret.Msg = ResUI.CheckServerSettings; - return ret; - } - - ret.Msg = ResUI.InitialConfiguration; - - var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (result.IsNullOrEmpty() || txtOutbound.IsNullOrEmpty()) - { - ret.Msg = ResUI.FailedGetDefaultConfiguration; - return ret; - } - - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) - { - ret.Msg = ResUI.FailedGenDefaultConfiguration; - return ret; - } - List lstIpEndPoints = new(); - List lstTcpConns = new(); - try - { - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); - lstIpEndPoints.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); - lstTcpConns.AddRange(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()); - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - await GenLog(v2rayConfig); - v2rayConfig.inbounds.Clear(); - v2rayConfig.outbounds.Clear(); - v2rayConfig.routing.rules.Clear(); + GenLog(); + _coreConfig.inbounds.Clear(); + _coreConfig.outbounds.Clear(); + _coreConfig.routing.rules.Clear(); var initPort = AppManager.Instance.GetLocalPort(EInboundProtocol.speedtest); @@ -302,8 +120,9 @@ public partial class CoreConfigV2rayService(Config config) { continue; } - var item = await AppManager.Instance.GetProfileItem(it.IndexId); - if (item is null || item.IsComplex() || !item.IsValid()) + var actIndexId = context.ServerTestItemMap.GetValueOrDefault(it.IndexId, it.IndexId); + var item = context.AllProxiesMap.GetValueOrDefault(actIndexId); + if (item is null || item.ConfigType is EConfigType.Custom || !item.IsValid()) { continue; } @@ -342,27 +161,40 @@ public partial class CoreConfigV2rayService(Config config) protocol = EInboundProtocol.mixed.ToString(), }; inbound.tag = inbound.protocol + inbound.port.ToString(); - v2rayConfig.inbounds.Add(inbound); + _coreConfig.inbounds.Add(inbound); + var tag = Global.ProxyTag + inbound.port.ToString(); + var isBalancer = false; //outbound - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(item, outbound); - outbound.tag = Global.ProxyTag + inbound.port.ToString(); - v2rayConfig.outbounds.Add(outbound); + var proxyOutbounds = + new CoreConfigV2rayService(context with { Node = item }).BuildAllProxyOutbounds(tag); + _coreConfig.outbounds.AddRange(proxyOutbounds); + if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) + { + isBalancer = true; + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad, tag); + GenBalancer(multipleLoad, tag); + } //rule RulesItem4Ray rule = new() { inboundTag = new List { inbound.tag }, - outboundTag = outbound.tag, + outboundTag = tag, type = "field" }; - v2rayConfig.routing.rules.Add(rule); + if (isBalancer) + { + rule.balancerTag = tag; + rule.outboundTag = null; + } + _coreConfig.routing.rules.Add(rule); } //ret.Msg =string.Format(ResUI.SuccessfulConfiguration"), node.getSummary()); ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); return ret; } catch (Exception ex) @@ -373,21 +205,21 @@ public partial class CoreConfigV2rayService(Config config) } } - public async Task GenerateClientSpeedtestConfig(ProfileItem node, int port) + public RetResult GenerateClientSpeedtestConfig(int port) { var ret = new RetResult(); try { - if (node == null - || !node.IsValid()) + if (_node == null + || !_node.IsValid()) { ret.Msg = ResUI.CheckServerSettings; return ret; } - if (node.GetNetwork() is nameof(ETransport.quic)) + if (_node.GetNetwork() is nameof(ETransport.quic)) { - ret.Msg = ResUI.Incorrectconfiguration + $" - {node.GetNetwork()}"; + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; return ret; } @@ -398,20 +230,20 @@ public partial class CoreConfigV2rayService(Config config) return ret; } - var v2rayConfig = JsonUtils.Deserialize(result); - if (v2rayConfig == null) + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) { ret.Msg = ResUI.FailedGenDefaultConfiguration; return ret; } - await GenLog(v2rayConfig); - await GenOutbound(node, v2rayConfig.outbounds.First()); - await GenMoreOutbounds(node, v2rayConfig); + GenLog(); + GenOutbounds(); - v2rayConfig.routing.rules.Clear(); - v2rayConfig.inbounds.Clear(); - v2rayConfig.inbounds.Add(new() + _coreConfig.routing.domainStrategy = Global.AsIs; + _coreConfig.routing.rules.Clear(); + _coreConfig.inbounds.Clear(); + _coreConfig.inbounds.Add(new() { tag = $"{EInboundProtocol.socks}{port}", listen = Global.Loopback, @@ -419,9 +251,112 @@ public partial class CoreConfigV2rayService(Config config) protocol = EInboundProtocol.mixed.ToString(), }); + _coreConfig.routing.rules.Add(BuildFinalRule()); + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); ret.Success = true; - ret.Data = JsonUtils.Serialize(v2rayConfig); + ret.Data = JsonUtils.Serialize(_coreConfig); + return ret; + } + catch (Exception ex) + { + Logging.SaveLog(_tag, ex); + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + } + + public RetResult GenerateClientProxyRelayConfig() + { + var ret = new RetResult(); + try + { + if (_node == null + || !_node.IsValid()) + { + ret.Msg = ResUI.CheckServerSettings; + return ret; + } + + if (_node.GetNetwork() is nameof(ETransport.quic)) + { + ret.Msg = ResUI.Incorrectconfiguration + $" - {_node.GetNetwork()}"; + return ret; + } + + var result = EmbedUtils.GetEmbedText(Global.V2raySampleClient); + if (result.IsNullOrEmpty()) + { + ret.Msg = ResUI.FailedGetDefaultConfiguration; + return ret; + } + + _coreConfig = JsonUtils.Deserialize(result); + if (_coreConfig == null) + { + ret.Msg = ResUI.FailedGenDefaultConfiguration; + return ret; + } + + GenLog(); + _coreConfig.outbounds.Clear(); + GenOutbounds(); + + var protectNode = new ProfileItem() + { + CoreType = ECoreType.Xray, + ConfigType = EConfigType.Shadowsocks, + Address = Global.Loopback, + Port = context.TunProtectSsPort, + Password = Global.None, + }; + protectNode.SetProtocolExtra(protectNode.GetProtocolExtra() with + { + SsMethod = Global.None, + }); + + foreach (var outbound in _coreConfig.outbounds.Where(outbound => outbound.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)) + { + outbound.streamSettings ??= new StreamSettings4Ray(); + outbound.streamSettings.sockopt ??= new Sockopt4Ray(); + outbound.streamSettings.sockopt.dialerProxy = "tun-project-ss"; + } + _coreConfig.outbounds.Add(new CoreConfigV2rayService(context with + { + Node = protectNode, + }).BuildProxyOutbound("tun-project-ss")); + + var hasBalancer = _coreConfig.routing.balancers is { Count: > 0 }; + _coreConfig.routing.rules = + [ + new() + { + inboundTag = new List { "proxy-relay-ss" }, + outboundTag = hasBalancer ? null : Global.ProxyTag, + balancerTag = hasBalancer ? Global.ProxyTag + Global.BalancerTagSuffix: null, + type = "field" + } + ]; + _coreConfig.inbounds.Clear(); + + var configNode = JsonUtils.ParseJson(JsonUtils.Serialize(_coreConfig))!; + configNode["inbounds"]!.AsArray().Add(new + { + listen = Global.Loopback, + port = context.ProxyRelaySsPort, + protocol = "shadowsocks", + settings = new + { + network = "tcp,udp", + method = Global.None, + password = Global.None, + }, + tag = "proxy-relay-ss", + }); + + ret.Msg = string.Format(ResUI.SuccessfulConfiguration, ""); + ret.Success = true; + ret.Data = JsonUtils.Serialize(configNode); return ret; } catch (Exception ex) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs index 35a220c6..f294de46 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayBalancerService.cs @@ -2,17 +2,17 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenObservatory(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) + private void GenObservatory(EMultipleLoad multipleLoad, string baseTagName = Global.ProxyTag) { // Collect all existing subject selectors from both observatories var subjectSelectors = new List(); - subjectSelectors.AddRange(v2rayConfig.burstObservatory?.subjectSelector ?? []); - subjectSelectors.AddRange(v2rayConfig.observatory?.subjectSelector ?? []); + subjectSelectors.AddRange(_coreConfig.burstObservatory?.subjectSelector ?? []); + subjectSelectors.AddRange(_coreConfig.observatory?.subjectSelector ?? []); // Case 1: exact match already exists -> nothing to do if (subjectSelectors.Any(baseTagName.StartsWith)) { - return await Task.FromResult(0); + return; } // Case 2: prefix match exists -> reuse it and move to the first position @@ -21,28 +21,28 @@ public partial class CoreConfigV2rayService { baseTagName = matched; - if (v2rayConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) + if (_coreConfig.burstObservatory?.subjectSelector?.Contains(baseTagName) == true) { - v2rayConfig.burstObservatory.subjectSelector.Remove(baseTagName); - v2rayConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); + _coreConfig.burstObservatory.subjectSelector.Remove(baseTagName); + _coreConfig.burstObservatory.subjectSelector.Insert(0, baseTagName); } - if (v2rayConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) + if (_coreConfig.observatory?.subjectSelector?.Contains(baseTagName) == true) { - v2rayConfig.observatory.subjectSelector.Remove(baseTagName); - v2rayConfig.observatory.subjectSelector.Insert(0, baseTagName); + _coreConfig.observatory.subjectSelector.Remove(baseTagName); + _coreConfig.observatory.subjectSelector.Insert(0, baseTagName); } - return await Task.FromResult(0); + return; } // Case 3: need to create or insert based on multipleLoad type if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback) { - if (v2rayConfig.burstObservatory is null) + if (_coreConfig.burstObservatory is null) { // Create new burst observatory with default ping config - v2rayConfig.burstObservatory = new BurstObservatory4Ray + _coreConfig.burstObservatory = new BurstObservatory4Ray { subjectSelector = [baseTagName], pingConfig = new() @@ -56,16 +56,16 @@ public partial class CoreConfigV2rayService } else { - v2rayConfig.burstObservatory.subjectSelector ??= new(); - v2rayConfig.burstObservatory.subjectSelector.Add(baseTagName); + _coreConfig.burstObservatory.subjectSelector ??= new(); + _coreConfig.burstObservatory.subjectSelector.Add(baseTagName); } } else if (multipleLoad is EMultipleLoad.LeastPing) { - if (v2rayConfig.observatory is null) + if (_coreConfig.observatory is null) { // Create new observatory with default probe config - v2rayConfig.observatory = new Observatory4Ray + _coreConfig.observatory = new Observatory4Ray { subjectSelector = [baseTagName], probeUrl = AppManager.Instance.Config.SpeedTestItem.SpeedPingTestUrl, @@ -75,15 +75,13 @@ public partial class CoreConfigV2rayService } else { - v2rayConfig.observatory.subjectSelector ??= new(); - v2rayConfig.observatory.subjectSelector.Add(baseTagName); + _coreConfig.observatory.subjectSelector ??= new(); + _coreConfig.observatory.subjectSelector.Add(baseTagName); } } - - return await Task.FromResult(0); } - private async Task GenBalancer(V2rayConfig v2rayConfig, EMultipleLoad multipleLoad, string selector = Global.ProxyTag) + private void GenBalancer(EMultipleLoad multipleLoad, string selector = Global.ProxyTag) { var strategyType = multipleLoad switch { @@ -107,8 +105,7 @@ public partial class CoreConfigV2rayService }, tag = balancerTag, }; - v2rayConfig.routing.balancers ??= new(); - v2rayConfig.routing.balancers.Add(balancer); - return await Task.FromResult(balancerTag); + _coreConfig.routing.balancers ??= new(); + _coreConfig.routing.balancers.Add(balancer); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs index 1f2583ff..544d1d2e 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayConfigTemplateService.cs @@ -2,35 +2,39 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task ApplyFullConfigTemplate(V2rayConfig v2rayConfig) + private string ApplyFullConfigTemplate() { - var fullConfigTemplate = await AppManager.Instance.GetFullConfigTemplateItem(ECoreType.Xray); + var fullConfigTemplate = context.FullConfigTemplate; if (fullConfigTemplate == null || !fullConfigTemplate.Enabled || fullConfigTemplate.Config.IsNullOrEmpty()) { - return JsonUtils.Serialize(v2rayConfig); + return JsonUtils.Serialize(_coreConfig); } var fullConfigTemplateNode = JsonNode.Parse(fullConfigTemplate.Config); if (fullConfigTemplateNode == null) { - return JsonUtils.Serialize(v2rayConfig); + return JsonUtils.Serialize(_coreConfig); } // Handle balancer and rules modifications (for multiple load scenarios) - if (v2rayConfig.routing?.balancers?.Count > 0) + if (_coreConfig.routing?.balancers?.Count > 0) { - var balancer = v2rayConfig.routing.balancers.First(); + var balancer = + _coreConfig.routing.balancers.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null); // Modify existing rules in custom config - var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; - if (rulesNode != null) + if (balancer != null) { - foreach (var rule in rulesNode.AsArray()) + var rulesNode = fullConfigTemplateNode["routing"]?["rules"]; + if (rulesNode != null) { - if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + foreach (var rule in rulesNode.AsArray()) { - rule.AsObject().Remove("outboundTag"); - rule["balancerTag"] = balancer.tag; + if (rule["outboundTag"]?.GetValue() == Global.ProxyTag) + { + rule.AsObject().Remove("outboundTag"); + rule["balancerTag"] = balancer.tag; + } } } } @@ -44,7 +48,7 @@ public partial class CoreConfigV2rayService // Handle balancers - append instead of override if (fullConfigTemplateNode["routing"]["balancers"] is JsonArray customBalancersNode) { - if (JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)) is JsonArray newBalancers) + if (JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)) is JsonArray newBalancers) { foreach (var balancerNode in newBalancers) { @@ -54,33 +58,33 @@ public partial class CoreConfigV2rayService } else { - fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.routing.balancers)); + fullConfigTemplateNode["routing"]["balancers"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.routing.balancers)); } } - if (v2rayConfig.observatory != null) + if (_coreConfig.observatory != null) { if (fullConfigTemplateNode["observatory"] == null) { - fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.observatory)); + fullConfigTemplateNode["observatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.observatory)); } else { - var subjectSelector = v2rayConfig.observatory.subjectSelector; + var subjectSelector = _coreConfig.observatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["observatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["observatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } } - if (v2rayConfig.burstObservatory != null) + if (_coreConfig.burstObservatory != null) { if (fullConfigTemplateNode["burstObservatory"] == null) { - fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(v2rayConfig.burstObservatory)); + fullConfigTemplateNode["burstObservatory"] = JsonNode.Parse(JsonUtils.Serialize(_coreConfig.burstObservatory)); } else { - var subjectSelector = v2rayConfig.burstObservatory.subjectSelector; + var subjectSelector = _coreConfig.burstObservatory.subjectSelector; subjectSelector.AddRange(fullConfigTemplateNode["burstObservatory"]?["subjectSelector"]?.AsArray()?.Select(x => x?.GetValue()) ?? []); fullConfigTemplateNode["burstObservatory"]["subjectSelector"] = JsonNode.Parse(JsonUtils.Serialize(subjectSelector.Distinct().ToList())); } @@ -88,7 +92,7 @@ public partial class CoreConfigV2rayService var customOutboundsNode = new JsonArray(); - foreach (var outbound in v2rayConfig.outbounds) + foreach (var outbound in _coreConfig.outbounds) { if (outbound.protocol.ToLower() is "blackhole" or "dns" or "freedom") { @@ -97,8 +101,8 @@ public partial class CoreConfigV2rayService continue; } } - else if ((!fullConfigTemplate.ProxyDetour.IsNullOrEmpty()) - && ((outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true) == true)) + else if (!fullConfigTemplate.ProxyDetour.IsNullOrEmpty() + && (outbound.streamSettings?.sockopt?.dialerProxy.IsNullOrEmpty() ?? true)) { var outboundAddress = outbound.settings?.servers?.FirstOrDefault()?.address ?? outbound.settings?.vnext?.FirstOrDefault()?.address @@ -123,6 +127,6 @@ public partial class CoreConfigV2rayService fullConfigTemplateNode["outbounds"] = customOutboundsNode; - return await Task.FromResult(JsonUtils.Serialize(fullConfigTemplateNode)); + return JsonUtils.Serialize(fullConfigTemplateNode); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs index 7de97240..1d31f7b2 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayDnsService.cs @@ -2,46 +2,45 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenDns(ProfileItem? node, V2rayConfig v2rayConfig) + private void GenDns() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); + var item = context.RawDnsItem; if (item is { Enabled: true }) { - var result = await GenDnsCompatible(node, v2rayConfig); + GenDnsCustom(); - if (v2rayConfig.routing.domainStrategy != Global.IPIfNonMatch) + if (_coreConfig.routing.domainStrategy != Global.IPIfNonMatch) { - return result; + return; } // DNS routing - var dnsObj = JsonUtils.SerializeToNode(v2rayConfig.dns); + var dnsObj = JsonUtils.SerializeToNode(_coreConfig.dns); if (dnsObj == null) { - return result; + return; } dnsObj["tag"] = Global.DnsTag; - v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(dnsObj)); - v2rayConfig.routing.rules.Add(new RulesItem4Ray + _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(dnsObj)); + _coreConfig.routing.rules.Add(new RulesItem4Ray { type = "field", inboundTag = new List { Global.DnsTag }, outboundTag = Global.ProxyTag, }); - - return result; + return; } - var simpleDnsItem = _config.SimpleDNSItem; - var dnsItem = v2rayConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); + var simpleDnsItem = context.SimpleDnsItem; + var dnsItem = _coreConfig.dns is Dns4Ray dns4Ray ? dns4Ray : new Dns4Ray(); var strategy4Freedom = simpleDnsItem?.Strategy4Freedom ?? Global.AsIs; //Outbound Freedom domainStrategy if (strategy4Freedom.IsNotEmpty() && strategy4Freedom != Global.AsIs) { - var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new() @@ -59,80 +58,40 @@ public partial class CoreConfigV2rayService var xraySupportConfigTypeNames = Global.XraySupportConfigType .Select(x => x == EConfigType.Hysteria2 ? "hysteria" : Global.ProtocolTypes[x]) .ToHashSet(); - v2rayConfig.outbounds + _coreConfig.outbounds .Where(t => xraySupportConfigTypeNames.Contains(t.protocol)) .ToList() .ForEach(outbound => outbound.targetStrategy = strategy4Proxy); } - await GenDnsServers(node, dnsItem, simpleDnsItem); - await GenDnsHosts(dnsItem, simpleDnsItem); + FillDnsServers(dnsItem); + FillDnsHosts(dnsItem); dnsItem.serveStale = simpleDnsItem?.ServeStale is true ? true : null; dnsItem.enableParallelQuery = simpleDnsItem?.ParallelQuery is true ? true : null; - if (v2rayConfig.routing.domainStrategy == Global.IPIfNonMatch) + // DNS routing + var finalRule = BuildFinalRule(); + dnsItem.tag = Global.DnsTag; + _coreConfig.routing.rules.Add(new() { - // DNS routing - dnsItem.tag = Global.DnsTag; - v2rayConfig.routing.rules.Add(new RulesItem4Ray - { - type = "field", - inboundTag = new List { Global.DnsTag }, - outboundTag = Global.ProxyTag, - }); - } + type = "field", + inboundTag = [Global.DnsTag], + outboundTag = finalRule.outboundTag, + balancerTag = finalRule.balancerTag + }); - v2rayConfig.dns = dnsItem; + _coreConfig.dns = dnsItem; } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsServers(ProfileItem? node, Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) + private void FillDnsServers(Dns4Ray dnsItem) { - static List ParseDnsAddresses(string? dnsInput, string defaultAddress) - { - var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') - .Select(addr => addr.Trim()) - .Where(addr => !string.IsNullOrEmpty(addr)) - .Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr) - .Distinct() - .ToList() ?? new List { defaultAddress }; - return addresses.Count > 0 ? addresses : new List { defaultAddress }; - } - - static object? CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) - { - var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress); - var domainFinal = dnsAddress; - int? portFinal = null; - if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) - { - domainFinal = domain; - portFinal = port > 0 ? port : null; - } - else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase)) - { - domainFinal = scheme + "://" + domain; - portFinal = port > 0 ? port : null; - } - var dnsServer = new DnsServer4Ray - { - address = domainFinal, - port = portFinal, - skipFallback = true, - domains = domains.Count > 0 ? domains : null, - expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null - }; - return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }); - } + var simpleDNSItem = context.SimpleDnsItem; var directDNSAddress = ParseDnsAddresses(simpleDNSItem?.DirectDNS, Global.DomainDirectDNSAddress.First()); var remoteDNSAddress = ParseDnsAddresses(simpleDNSItem?.RemoteDNS, Global.DomainRemoteDNSAddress.First()); @@ -197,9 +156,9 @@ public partial class CoreConfigV2rayService } } - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; List? rules = null; - rules = JsonUtils.Deserialize>(routing.RuleSet) ?? []; + rules = JsonUtils.Deserialize>(routing?.RuleSet) ?? []; foreach (var item in rules) { if (!item.Enabled || item.Domain is null || item.Domain.Count == 0) @@ -246,42 +205,13 @@ public partial class CoreConfigV2rayService } } - if (Utils.IsDomain(node?.Address)) + if (context.ProtectDomainList.Count > 0) { - directDomainList.Add(node.Address); - } - - if (node?.Subid is not null) - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is not null) - { - foreach (var profile in new[] { subItem.PrevProfile, subItem.NextProfile }) - { - var profileNode = await AppManager.Instance.GetProfileItemViaRemarks(profile); - if (profileNode is not null - && Global.XraySupportConfigType.Contains(profileNode.ConfigType) - && Utils.IsDomain(profileNode.Address)) - { - directDomainList.Add(profileNode.Address); - } - } - } + directDomainList.AddRange(context.ProtectDomainList); } dnsItem.servers ??= []; - void AddDnsServers(List dnsAddresses, List domains, List? expectedIPs = null) - { - if (domains.Count > 0) - { - foreach (var dnsAddress in dnsAddresses) - { - dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); - } - } - } - AddDnsServers(remoteDNSAddress, proxyDomainList); AddDnsServers(directDNSAddress, directDomainList); AddDnsServers(remoteDNSAddress, proxyGeositeList); @@ -292,23 +222,81 @@ public partial class CoreConfigV2rayService AddDnsServers(bootstrapDNSAddress, dnsServerDomains); } - var useDirectDns = rules?.LastOrDefault() is { } lastRule - && lastRule.OutboundTag == Global.DirectTag - && (lastRule.Port == "0-65535" - || lastRule.Network == "tcp,udp" - || lastRule.Ip?.Contains("0.0.0.0/0") == true); + var useDirectDns = false; + + if (rules?.LastOrDefault() is { } lastRule && lastRule.OutboundTag == Global.DirectTag) + { + var noDomain = lastRule.Domain == null || lastRule.Domain.Count == 0; + var noProcess = lastRule.Process == null || lastRule.Process.Count == 0; + var isAnyIp = lastRule.Ip == null || lastRule.Ip.Count == 0 || lastRule.Ip.Contains("0.0.0.0/0"); + var isAnyPort = string.IsNullOrEmpty(lastRule.Port) || lastRule.Port == "0-65535"; + var isAnyNetwork = string.IsNullOrEmpty(lastRule.Network) || lastRule.Network == "tcp,udp"; + useDirectDns = noDomain && noProcess && isAnyIp && isAnyPort && isAnyNetwork; + } var defaultDnsServers = useDirectDns ? directDNSAddress : remoteDNSAddress; dnsItem.servers.AddRange(defaultDnsServers); + return; - return 0; + static List ParseDnsAddresses(string? dnsInput, string defaultAddress) + { + var addresses = dnsInput?.Split(dnsInput.Contains(',') ? ',' : ';') + .Select(addr => addr.Trim()) + .Where(addr => !string.IsNullOrEmpty(addr)) + .Select(addr => addr.StartsWith("dhcp", StringComparison.OrdinalIgnoreCase) ? "localhost" : addr) + .Distinct() + .ToList() ?? [defaultAddress]; + return addresses.Count > 0 ? addresses : new List { defaultAddress }; + } + + static object? CreateDnsServer(string dnsAddress, List domains, List? expectedIPs = null) + { + var (domain, scheme, port, path) = Utils.ParseUrl(dnsAddress); + var domainFinal = dnsAddress; + int? portFinal = null; + if (scheme.IsNullOrEmpty() || scheme.StartsWith("udp", StringComparison.OrdinalIgnoreCase)) + { + domainFinal = domain; + portFinal = port > 0 ? port : null; + } + else if (scheme.StartsWith("tcp", StringComparison.OrdinalIgnoreCase)) + { + domainFinal = scheme + "://" + domain; + portFinal = port > 0 ? port : null; + } + var dnsServer = new DnsServer4Ray + { + address = domainFinal, + port = portFinal, + skipFallback = true, + domains = domains.Count > 0 ? domains : null, + expectedIPs = expectedIPs?.Count > 0 ? expectedIPs : null + }; + return JsonUtils.SerializeToNode(dnsServer, new JsonSerializerOptions + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }); + } + + void AddDnsServers(List dnsAddresses, List domains, List? expectedIPs = null) + { + if (domains.Count <= 0) + { + return; + } + foreach (var dnsAddress in dnsAddresses) + { + dnsItem.servers.Add(CreateDnsServer(dnsAddress, domains, expectedIPs)); + } + } } - private async Task GenDnsHosts(Dns4Ray dnsItem, SimpleDNSItem simpleDNSItem) + private void FillDnsHosts(Dns4Ray dnsItem) { + var simpleDNSItem = context.SimpleDnsItem; if (simpleDNSItem.AddCommonHosts == false && simpleDNSItem.UseSystemHosts == false && simpleDNSItem.Hosts.IsNullOrEmpty()) { - return await Task.FromResult(0); + return; } dnsItem.hosts ??= new Dictionary(); if (simpleDNSItem.AddCommonHosts == true) @@ -337,14 +325,13 @@ public partial class CoreConfigV2rayService { dnsItem.hosts[kvp.Key] = kvp.Value; } - return await Task.FromResult(0); } - private async Task GenDnsCompatible(ProfileItem? node, V2rayConfig v2rayConfig) + private void GenDnsCustom() { try { - var item = await AppManager.Instance.GetDNSItem(ECoreType.Xray); + var item = context.RawDnsItem; var normalDNS = item?.NormalDNS; var domainStrategy4Freedom = item?.DomainStrategy4Freedom; if (normalDNS.IsNullOrEmpty()) @@ -355,7 +342,7 @@ public partial class CoreConfigV2rayService //Outbound Freedom domainStrategy if (domainStrategy4Freedom.IsNotEmpty()) { - var outbound = v2rayConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); + var outbound = _coreConfig.outbounds.FirstOrDefault(t => t is { protocol: "freedom", tag: Global.DirectTag }); if (outbound != null) { outbound.settings = new(); @@ -410,63 +397,37 @@ public partial class CoreConfigV2rayService } } - await GenDnsDomainsCompatible(node, obj, item); + FillDnsDomainsCustom(obj); - v2rayConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); + _coreConfig.dns = JsonUtils.Deserialize(JsonUtils.Serialize(obj)); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenDnsDomainsCompatible(ProfileItem? node, JsonNode dns, DNSItem? dnsItem) + private void FillDnsDomainsCustom(JsonNode dns) { - if (node == null) - { - return 0; - } var servers = dns["servers"]; - if (servers != null) + if (servers == null) { - var domainList = new List(); - if (Utils.IsDomain(node.Address)) - { - domainList.Add(node.Address); - } - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is not null) - { - // Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.SingboxSupportConfigType.Contains(prevNode.ConfigType) - && Utils.IsDomain(prevNode.Address)) - { - domainList.Add(prevNode.Address); - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.SingboxSupportConfigType.Contains(nextNode.ConfigType) - && Utils.IsDomain(nextNode.Address)) - { - domainList.Add(nextNode.Address); - } - } - if (domainList.Count > 0) - { - var dnsServer = new DnsServer4Ray() - { - address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, - skipFallback = true, - domains = domainList - }; - servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); - } + return; } - return await Task.FromResult(0); + + var domainList = context.ProtectDomainList; + if (domainList.Count <= 0) + { + return; + } + + var dnsItem = context.RawDnsItem; + var dnsServer = new DnsServer4Ray() + { + address = string.IsNullOrEmpty(dnsItem?.DomainDNSAddress) ? Global.DomainPureIPDNSAddress.FirstOrDefault() : dnsItem?.DomainDNSAddress, + skipFallback = true, + domains = domainList.ToList(), + }; + servers.AsArray().Add(JsonUtils.SerializeToNode(dnsServer)); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs index 2cbdfe88..7ae12c7c 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayInboundService.cs @@ -2,35 +2,35 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenInbounds(V2rayConfig v2rayConfig) + private void GenInbounds() { try { var listen = "0.0.0.0"; - v2rayConfig.inbounds = []; + _coreConfig.inbounds = []; - var inbound = GetInbound(_config.Inbound.First(), EInboundProtocol.socks, true); - v2rayConfig.inbounds.Add(inbound); + var inbound = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks, true); + _coreConfig.inbounds.Add(inbound); if (_config.Inbound.First().SecondLocalPortEnabled) { - var inbound2 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); - v2rayConfig.inbounds.Add(inbound2); + var inbound2 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks2, true); + _coreConfig.inbounds.Add(inbound2); } if (_config.Inbound.First().AllowLANConn) { if (_config.Inbound.First().NewPort4LAN) { - var inbound3 = GetInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); + var inbound3 = BuildInbound(_config.Inbound.First(), EInboundProtocol.socks3, true); inbound3.listen = listen; - v2rayConfig.inbounds.Add(inbound3); + _coreConfig.inbounds.Add(inbound3); //auth if (_config.Inbound.First().User.IsNotEmpty() && _config.Inbound.First().Pass.IsNotEmpty()) { inbound3.settings.auth = "password"; - inbound3.settings.accounts = new List { new AccountsItem4Ray() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; + inbound3.settings.accounts = new List { new() { user = _config.Inbound.First().User, pass = _config.Inbound.First().Pass } }; } } else @@ -43,10 +43,9 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private Inbounds4Ray GetInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) + private Inbounds4Ray BuildInbound(InItem inItem, EInboundProtocol protocol, bool bSocks) { var result = EmbedUtils.GetEmbedText(Global.V2raySampleInbound); if (result.IsNullOrEmpty()) diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs index 5b9344fb..86f4b2c2 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayLogService.cs @@ -2,28 +2,27 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenLog(V2rayConfig v2rayConfig) + private void GenLog() { try { if (_config.CoreBasicItem.LogEnabled) { var dtNow = DateTime.Now; - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); - v2rayConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + _coreConfig.log.access = Utils.GetLogPath($"Vaccess_{dtNow:yyyy-MM-dd}.txt"); + _coreConfig.log.error = Utils.GetLogPath($"Verror_{dtNow:yyyy-MM-dd}.txt"); } else { - v2rayConfig.log.loglevel = _config.CoreBasicItem.Loglevel; - v2rayConfig.log.access = null; - v2rayConfig.log.error = null; + _coreConfig.log.loglevel = _config.CoreBasicItem.Loglevel; + _coreConfig.log.access = null; + _coreConfig.log.error = null; } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs index 7f9c26ca..c8862648 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayOutboundService.cs @@ -2,13 +2,96 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenOutbound(ProfileItem node, Outbounds4Ray outbound) + private void GenOutbounds() + { + var proxyOutboundList = BuildAllProxyOutbounds(); + _coreConfig.outbounds.InsertRange(0, proxyOutboundList); + if (proxyOutboundList.Count(n => n.tag.StartsWith(Global.ProxyTag)) > 1) + { + var multipleLoad = _node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad); + GenBalancer(multipleLoad); + } + } + + private List BuildAllProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + if (_node.ConfigType.IsGroupType()) + { + proxyOutboundList.AddRange(BuildGroupProxyOutbounds(baseTagName)); + } + else + { + proxyOutboundList.Add(BuildProxyOutbound(baseTagName)); + } + + if (_config.CoreBasicItem.EnableFragment) + { + var fragmentOutbound = new Outbounds4Ray + { + protocol = "freedom", + tag = $"frag-{baseTagName}", + settings = new() + { + fragment = new() + { + packets = _config.Fragment4RayItem?.Packets, + length = _config.Fragment4RayItem?.Length, + interval = _config.Fragment4RayItem?.Interval + } + } + }; + var actOutboundWithTlsList = + proxyOutboundList.Where(n => n.streamSettings?.security.IsNullOrEmpty() == false + && (n.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true)).ToList(); + if (actOutboundWithTlsList.Count > 0) + { + proxyOutboundList.Add(fragmentOutbound); + } + foreach (var outbound in actOutboundWithTlsList) + { + outbound.streamSettings.sockopt = new() + { + dialerProxy = fragmentOutbound.tag + }; + } + } + return proxyOutboundList; + } + + private List BuildGroupProxyOutbounds(string baseTagName = Global.ProxyTag) + { + var proxyOutboundList = new List(); + switch (_node.ConfigType) + { + case EConfigType.PolicyGroup: + proxyOutboundList.AddRange(BuildOutboundsList(baseTagName)); + break; + + case EConfigType.ProxyChain: + proxyOutboundList.AddRange(BuildChainOutboundsList(baseTagName)); + break; + } + return proxyOutboundList; + } + + private Outbounds4Ray BuildProxyOutbound(string baseTagName = Global.ProxyTag) + { + var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); + var outbound = JsonUtils.Deserialize(txtOutbound); + FillOutbound(outbound); + outbound.tag = baseTagName; + return outbound; + } + + private void FillOutbound(Outbounds4Ray outbound) { try { - var protocolExtra = node.GetProtocolExtra(); - var muxEnabled = node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; - switch (node.ConfigType) + var protocolExtra = _node.GetProtocolExtra(); + var muxEnabled = _node.MuxEnabled ?? _config.CoreBasicItem.MuxEnabled; + switch (_node.ConfigType) { case EConfigType.VMess: { @@ -22,8 +105,8 @@ public partial class CoreConfigV2rayService { vnextItem = outbound.settings.vnext.First(); } - vnextItem.address = node.Address; - vnextItem.port = node.Port; + vnextItem.address = _node.Address; + vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) @@ -36,7 +119,7 @@ public partial class CoreConfigV2rayService usersItem = vnextItem.users.First(); } - usersItem.id = node.Password; + usersItem.id = _node.Password; usersItem.alterId = int.TryParse(protocolExtra?.AlterId, out var result) ? result : 0; usersItem.email = Global.UserEMail; if (Global.VmessSecurities.Contains(protocolExtra.VmessSecurity)) @@ -48,7 +131,7 @@ public partial class CoreConfigV2rayService usersItem.security = Global.DefaultSecurity; } - await GenOutboundMux(node, outbound, muxEnabled, muxEnabled); + FillOutboundMux(outbound, muxEnabled, muxEnabled); outbound.settings.servers = null; break; @@ -65,16 +148,16 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Password; - serversItem.method = AppManager.Instance.GetShadowsocksSecurities(node).Contains(protocolExtra.SsMethod) + serversItem.address = _node.Address; + serversItem.port = _node.Port; + serversItem.password = _node.Password; + serversItem.method = AppManager.Instance.GetShadowsocksSecurities(_node).Contains(protocolExtra.SsMethod) ? protocolExtra.SsMethod : "none"; serversItem.ota = false; serversItem.level = 1; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -92,25 +175,25 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; + serversItem.address = _node.Address; + serversItem.port = _node.Port; serversItem.method = null; serversItem.password = null; - if (node.Username.IsNotEmpty() - && node.Password.IsNotEmpty()) + if (_node.Username.IsNotEmpty() + && _node.Password.IsNotEmpty()) { SocksUsersItem4Ray socksUsersItem = new() { - user = node.Username ?? "", - pass = node.Password, + user = _node.Username ?? "", + pass = _node.Password, level = 1 }; serversItem.users = new List() { socksUsersItem }; } - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -127,8 +210,8 @@ public partial class CoreConfigV2rayService { vnextItem = outbound.settings.vnext.First(); } - vnextItem.address = node.Address; - vnextItem.port = node.Port; + vnextItem.address = _node.Address; + vnextItem.port = _node.Port; UsersItem4Ray usersItem; if (vnextItem.users.Count <= 0) @@ -140,7 +223,7 @@ public partial class CoreConfigV2rayService { usersItem = vnextItem.users.First(); } - usersItem.id = node.Password; + usersItem.id = _node.Password; usersItem.email = Global.UserEMail; usersItem.encryption = protocolExtra.VlessEncryption; @@ -150,7 +233,7 @@ public partial class CoreConfigV2rayService } else { - await GenOutboundMux(node, outbound, false, muxEnabled); + FillOutboundMux(outbound, false, muxEnabled); } outbound.settings.servers = null; break; @@ -167,14 +250,14 @@ public partial class CoreConfigV2rayService { serversItem = outbound.settings.servers.First(); } - serversItem.address = node.Address; - serversItem.port = node.Port; - serversItem.password = node.Password; + serversItem.address = _node.Address; + serversItem.port = _node.Port; + serversItem.password = _node.Password; serversItem.ota = false; serversItem.level = 1; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); outbound.settings.vnext = null; break; @@ -184,8 +267,8 @@ public partial class CoreConfigV2rayService outbound.settings = new() { version = 2, - address = node.Address, - port = node.Port, + address = _node.Address, + port = _node.Port, }; outbound.settings.vnext = null; outbound.settings.servers = null; @@ -193,7 +276,7 @@ public partial class CoreConfigV2rayService } case EConfigType.WireGuard: { - var address = node.Address; + var address = _node.Address; if (Utils.IsIpv6(address)) { address = $"[{address}]"; @@ -201,12 +284,12 @@ public partial class CoreConfigV2rayService var peer = new WireguardPeer4Ray { publicKey = protocolExtra.WgPublicKey ?? "", - endpoint = address + ":" + node.Port.ToString() + endpoint = address + ":" + _node.Port.ToString() }; var setting = new Outboundsettings4Ray { address = Utils.String2List(protocolExtra.WgInterfaceAddress), - secretKey = node.Password, + secretKey = _node.Password, reserved = Utils.String2List(protocolExtra.WgReserved)?.Select(int.Parse).ToList(), mtu = protocolExtra.WgMtu > 0 ? protocolExtra.WgMtu : Global.TunMtus.First(), peers = [peer] @@ -218,21 +301,20 @@ public partial class CoreConfigV2rayService } } - outbound.protocol = Global.ProtocolTypes[node.ConfigType]; - if (node.ConfigType == EConfigType.Hysteria2) + outbound.protocol = Global.ProtocolTypes[_node.ConfigType]; + if (_node.ConfigType == EConfigType.Hysteria2) { outbound.protocol = "hysteria"; } - await GenBoundStreamSettings(node, outbound); + FillBoundStreamSettings(outbound); } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenOutboundMux(ProfileItem node, Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) + private void FillOutboundMux(Outbounds4Ray outbound, bool enabledTCP = false, bool enabledUDP = false) { try { @@ -255,48 +337,40 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenBoundStreamSettings(ProfileItem node, Outbounds4Ray outbound) + private void FillBoundStreamSettings(Outbounds4Ray outbound) { try { var streamSettings = outbound.streamSettings; - var network = node.GetNetwork(); - if (node.ConfigType == EConfigType.Hysteria2) + var network = _node.GetNetwork(); + if (_node.ConfigType == EConfigType.Hysteria2) { network = "hysteria"; } streamSettings.network = network; - var host = node.RequestHost.TrimEx(); - var path = node.Path.TrimEx(); - var sni = node.Sni.TrimEx(); + var host = _node.RequestHost.TrimEx(); + var path = _node.Path.TrimEx(); + var sni = _node.Sni.TrimEx(); var useragent = ""; if (!_config.CoreBasicItem.DefUserAgent.IsNullOrEmpty()) { - try - { - useragent = Global.UserAgentTexts[_config.CoreBasicItem.DefUserAgent]; - } - catch (KeyNotFoundException) - { - useragent = _config.CoreBasicItem.DefUserAgent; - } + useragent = Global.UserAgentTexts.GetValueOrDefault(_config.CoreBasicItem.DefUserAgent, _config.CoreBasicItem.DefUserAgent); } //if tls - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { - streamSettings.security = node.StreamSecurity; + streamSettings.security = _node.StreamSecurity; TlsSettings4Ray tlsSettings = new() { - allowInsecure = Utils.ToBool(node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : node.AllowInsecure), - alpn = node.GetAlpn(), - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, - echConfigList = node.EchConfigList.NullIfEmpty(), - echForceQuery = node.EchForceQuery.NullIfEmpty() + allowInsecure = Utils.ToBool(_node.AllowInsecure.IsNullOrEmpty() ? _config.CoreBasicItem.DefAllowInsecure.ToString().ToLower() : _node.AllowInsecure), + alpn = _node.GetAlpn(), + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, + echConfigList = _node.EchConfigList.NullIfEmpty(), + echForceQuery = _node.EchForceQuery.NullIfEmpty() }; if (sni.IsNotEmpty()) { @@ -306,7 +380,7 @@ public partial class CoreConfigV2rayService { tlsSettings.serverName = Utils.String2List(host)?.First(); } - var certs = CertPemManager.ParsePemChain(node.Cert); + var certs = CertPemManager.ParsePemChain(_node.Cert); if (certs.Count > 0) { var certsettings = new List(); @@ -323,27 +397,27 @@ public partial class CoreConfigV2rayService tlsSettings.disableSystemRoot = true; tlsSettings.allowInsecure = false; } - else if (!node.CertSha.IsNullOrEmpty()) + else if (!_node.CertSha.IsNullOrEmpty()) { - tlsSettings.pinnedPeerCertSha256 = node.CertSha; + tlsSettings.pinnedPeerCertSha256 = _node.CertSha; tlsSettings.allowInsecure = false; } streamSettings.tlsSettings = tlsSettings; } //if Reality - if (node.StreamSecurity == Global.StreamSecurityReality) + if (_node.StreamSecurity == Global.StreamSecurityReality) { - streamSettings.security = node.StreamSecurity; + streamSettings.security = _node.StreamSecurity; TlsSettings4Ray realitySettings = new() { - fingerprint = node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : node.Fingerprint, + fingerprint = _node.Fingerprint.IsNullOrEmpty() ? _config.CoreBasicItem.DefFingerprint : _node.Fingerprint, serverName = sni, - publicKey = node.PublicKey, - shortId = node.ShortId, - spiderX = node.SpiderX, - mldsa65Verify = node.Mldsa65Verify, + publicKey = _node.PublicKey, + shortId = _node.ShortId, + spiderX = _node.SpiderX, + mldsa65Verify = _node.Mldsa65Verify, show = false, }; @@ -367,14 +441,14 @@ public partial class CoreConfigV2rayService kcpSettings.readBufferSize = _config.KcpItem.ReadBufferSize; kcpSettings.writeBufferSize = _config.KcpItem.WriteBufferSize; streamSettings.finalmask ??= new(); - if (Global.KcpHeaderMaskMap.TryGetValue(node.HeaderType, out var header)) + if (Global.KcpHeaderMaskMap.TryGetValue(_node.HeaderType, out var header)) { streamSettings.finalmask.udp = [ new Mask4Ray { type = header, - settings = node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null + settings = _node.HeaderType == "dns" && !host.IsNullOrEmpty() ? new MaskSettings4Ray { domain = host } : null } ]; } @@ -444,17 +518,17 @@ public partial class CoreConfigV2rayService { xhttpSettings.host = host; } - if (node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(node.HeaderType)) + if (_node.HeaderType.IsNotEmpty() && Global.XhttpMode.Contains(_node.HeaderType)) { - xhttpSettings.mode = node.HeaderType; + xhttpSettings.mode = _node.HeaderType; } - if (node.Extra.IsNotEmpty()) + if (_node.Extra.IsNotEmpty()) { - xhttpSettings.extra = JsonUtils.ParseJson(node.Extra); + xhttpSettings.extra = JsonUtils.ParseJson(_node.Extra); } streamSettings.xhttpSettings = xhttpSettings; - await GenOutboundMux(node, outbound); + FillOutboundMux(outbound); break; //h2 @@ -478,11 +552,11 @@ public partial class CoreConfigV2rayService key = path, header = new Header4Ray { - type = node.HeaderType + type = _node.HeaderType } }; streamSettings.quicSettings = quicsettings; - if (node.StreamSecurity == Global.StreamSecurity) + if (_node.StreamSecurity == Global.StreamSecurity) { if (sni.IsNotEmpty()) { @@ -490,7 +564,7 @@ public partial class CoreConfigV2rayService } else { - streamSettings.tlsSettings.serverName = node.Address; + streamSettings.tlsSettings.serverName = _node.Address; } } break; @@ -500,7 +574,7 @@ public partial class CoreConfigV2rayService { authority = host.NullIfEmpty(), serviceName = path, - multiMode = node.HeaderType == Global.GrpcMultiMode, + multiMode = _node.HeaderType == Global.GrpcMultiMode, idle_timeout = _config.GrpcItem.IdleTimeout, health_check_timeout = _config.GrpcItem.HealthCheckTimeout, permit_without_stream = _config.GrpcItem.PermitWithoutStream, @@ -510,7 +584,7 @@ public partial class CoreConfigV2rayService break; case "hysteria": - var protocolExtra = node.GetProtocolExtra(); + var protocolExtra = _node.GetProtocolExtra(); var ports = protocolExtra?.Ports; int? upMbps = protocolExtra?.UpMbps is { } su and >= 0 ? su @@ -529,14 +603,14 @@ public partial class CoreConfigV2rayService { udpHop = new HysteriaUdpHop4Ray { - ports = ports.Replace(':', '-'), + port = ports.Replace(':', '-'), interval = hopInterval, }; } streamSettings.hysteriaSettings = new() { version = 2, - auth = node.Password, + auth = _node.Password, up = upMbps > 0 ? $"{upMbps}mbps" : null, down = downMbps > 0 ? $"{downMbps}mbps" : null, udphop = udpHop, @@ -557,13 +631,13 @@ public partial class CoreConfigV2rayService default: //tcp - if (node.HeaderType == Global.TcpHeaderHttp) + if (_node.HeaderType == Global.TcpHeaderHttp) { TcpSettings4Ray tcpSettings = new() { header = new Header4Ray { - type = node.HeaderType + type = _node.HeaderType } }; @@ -587,415 +661,142 @@ public partial class CoreConfigV2rayService } break; } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return 0; - } - private async Task GenGroupOutbound(ProfileItem node, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag, bool ignoreOriginChain = false) - { - try - { - if (!node.ConfigType.IsGroupType()) + if (!_node.Finalmask.IsNullOrEmpty()) { - return -1; - } - var hasCycle = await GroupProfileManager.HasCycle(node); - if (hasCycle) - { - return -1; - } - - var (childProfiles, profileExtraItem) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - return -1; - } - switch (node.ConfigType) - { - case EConfigType.PolicyGroup: - if (ignoreOriginChain) - { - await GenOutboundsList(childProfiles, v2rayConfig, baseTagName); - } - else - { - await GenOutboundsListWithChain(childProfiles, v2rayConfig, baseTagName); - } - break; - - case EConfigType.ProxyChain: - await GenChainOutboundsList(childProfiles, v2rayConfig, baseTagName); - break; - - default: - break; - } - - //add balancers - if (node.ConfigType == EConfigType.PolicyGroup) - { - var multipleLoad = profileExtraItem?.MultipleLoad ?? EMultipleLoad.LeastPing; - await GenObservatory(v2rayConfig, multipleLoad, baseTagName); - await GenBalancer(v2rayConfig, multipleLoad, baseTagName); + streamSettings.finalmask = JsonUtils.Deserialize(_node.Finalmask); } } catch (Exception ex) { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenMoreOutbounds(ProfileItem node, V2rayConfig v2rayConfig) + private List BuildOutboundsList(string baseTagName = Global.ProxyTag) { - //fragment proxy - if (_config.CoreBasicItem.EnableFragment - && v2rayConfig.outbounds.First().streamSettings?.security.IsNullOrEmpty() == false) + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) { - var fragmentOutbound = new Outbounds4Ray + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) { - protocol = "freedom", - tag = $"frag-{Global.ProxyTag}", - settings = new() - { - fragment = new() - { - packets = _config.Fragment4RayItem?.Packets, - length = _config.Fragment4RayItem?.Length, - interval = _config.Fragment4RayItem?.Interval - } - } - }; - - v2rayConfig.outbounds.Add(fragmentOutbound); - v2rayConfig.outbounds.First().streamSettings.sockopt = new() - { - dialerProxy = fragmentOutbound.tag - }; - return 0; - } - - if (node.Subid.IsNullOrEmpty()) - { - return 0; - } - try - { - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - if (subItem is null) - { - return 0; - } - - //current proxy - var outbound = v2rayConfig.outbounds.First(); - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - - //Previous proxy - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - string? prevOutboundTag = null; - if (prevNode is not null - && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevOutboundTag = $"prev-{Global.ProxyTag}"; - prevOutbound.tag = prevOutboundTag; - v2rayConfig.outbounds.Add(prevOutbound); - } - var nextOutbound = await GenChainOutbounds(subItem, outbound, prevOutboundTag); - - if (nextOutbound is not null) - { - v2rayConfig.outbounds.Insert(0, nextOutbound); + nodes.Add(node); } } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - private async Task GenOutboundsListWithChain(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) - { - try - { - // Get template and initialize list - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - return 0; - } - - var resultOutbounds = new List(); - var prevOutbounds = new List(); // Separate list for prev outbounds and fragment - - // Cache for chain proxies to avoid duplicate generation - var nextProxyCache = new Dictionary(); - var prevProxyTags = new Dictionary(); // Map from profile name to tag - var prevIndex = 0; // Index for prev outbounds - - // Process nodes - var index = 0; - foreach (var node in nodes) - { - index++; - - if (node.ConfigType.IsGroupType()) - { - var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{index}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; - continue; - } - - // Handle proxy chain - string? prevTag = null; - var currentOutbound = JsonUtils.Deserialize(txtOutbound); - var nextOutbound = nextProxyCache.GetValueOrDefault(node.Subid, null); - if (nextOutbound != null) - { - nextOutbound = JsonUtils.DeepCopy(nextOutbound); - } - - var subItem = await AppManager.Instance.GetSubItem(node.Subid); - - // current proxy - await GenOutbound(node, currentOutbound); - currentOutbound.tag = $"{baseTagName}-{index}"; - - if (!node.Subid.IsNullOrEmpty()) - { - if (prevProxyTags.TryGetValue(node.Subid, out var value)) - { - prevTag = value; // maybe null - } - else - { - var prevNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.PrevProfile); - if (prevNode is not null - && Global.XraySupportConfigType.Contains(prevNode.ConfigType)) - { - var prevOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(prevNode, prevOutbound); - prevTag = $"prev-{baseTagName}-{++prevIndex}"; - prevOutbound.tag = prevTag; - prevOutbounds.Add(prevOutbound); - } - prevProxyTags[node.Subid] = prevTag; - } - - nextOutbound = await GenChainOutbounds(subItem, currentOutbound, prevTag, nextOutbound); - if (!nextProxyCache.ContainsKey(node.Subid)) - { - nextProxyCache[node.Subid] = nextOutbound; - } - } - - if (nextOutbound is not null) - { - resultOutbounds.Add(nextOutbound); - } - resultOutbounds.Add(currentOutbound); - } - - // Merge results: first the main chain outbounds, then other outbounds, and finally utility outbounds - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(prevOutbounds); - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(prevOutbounds); - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - - return 0; - } - - /// - /// Generates a chained outbound configuration for the given subItem and outbound. - /// The outbound's tag must be set before calling this method. - /// Returns the next proxy's outbound configuration, which may be null if no next proxy exists. - /// - /// The subscription item containing proxy chain information. - /// The current outbound configuration. Its tag must be set before calling this method. - /// The tag of the previous outbound in the chain, if any. - /// The outbound for the next proxy in the chain, if already created. If null, will be created inside. - /// - /// The outbound configuration for the next proxy in the chain, or null if no next proxy exists. - /// - private async Task GenChainOutbounds(SubItem subItem, Outbounds4Ray outbound, string? prevOutboundTag, Outbounds4Ray? nextOutbound = null) - { - try - { - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - - if (!prevOutboundTag.IsNullOrEmpty()) - { - outbound.streamSettings.sockopt = new() - { - dialerProxy = prevOutboundTag - }; - } - - // Next proxy - var nextNode = await AppManager.Instance.GetProfileItemViaRemarks(subItem.NextProfile); - if (nextNode is not null - && Global.XraySupportConfigType.Contains(nextNode.ConfigType)) - { - if (nextOutbound == null) - { - nextOutbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(nextNode, nextOutbound); - } - nextOutbound.tag = outbound.tag; - - outbound.tag = $"mid-{outbound.tag}"; - nextOutbound.streamSettings.sockopt = new() - { - dialerProxy = outbound.tag - }; - } - return nextOutbound; - } - catch (Exception ex) - { - Logging.SaveLog(_tag, ex); - } - return null; - } - - private async Task GenOutboundsList(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) - { var resultOutbounds = new List(); for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; - if (node == null) - { - continue; - } + var currentTag = $"{baseTagName}-{i + 1}"; if (node.ConfigType.IsGroupType()) { - var (childProfiles, _) = await GroupProfileManager.GetChildProfileItems(node); - if (childProfiles.Count <= 0) - { - continue; - } - var childBaseTagName = $"{baseTagName}-{i + 1}"; - var ret = node.ConfigType switch - { - EConfigType.PolicyGroup => - await GenOutboundsListWithChain(childProfiles, v2rayConfig, childBaseTagName), - EConfigType.ProxyChain => - await GenChainOutboundsList(childProfiles, v2rayConfig, childBaseTagName), - _ => throw new NotImplementedException() - }; + var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + resultOutbounds.AddRange(childProfiles); continue; } - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) - { - break; - } - var outbound = JsonUtils.Deserialize(txtOutbound); - var result = await GenOutbound(node, outbound); - if (result != 0) - { - break; - } - outbound.tag = baseTagName + (i + 1).ToString(); + var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); + outbound.tag = currentTag; resultOutbounds.Add(outbound); } - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - return await Task.FromResult(0); + return resultOutbounds; } - private async Task GenChainOutboundsList(List nodes, V2rayConfig v2rayConfig, string baseTagName = Global.ProxyTag) + private List BuildChainOutboundsList(string baseTagName = Global.ProxyTag) { + var nodes = new List(); + foreach (var nodeId in Utils.String2List(_node.GetProtocolExtra().ChildItems) ?? []) + { + if (context.AllProxiesMap.TryGetValue(nodeId, out var node)) + { + nodes.Add(node); + } + } // Based on actual network flow instead of data packets var nodesReverse = nodes.AsEnumerable().Reverse().ToList(); var resultOutbounds = new List(); for (var i = 0; i < nodesReverse.Count; i++) { var node = nodesReverse[i]; - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - if (txtOutbound.IsNullOrEmpty()) + var currentTag = i == 0 ? baseTagName : $"chain-{baseTagName}-{i}"; + var dialerProxyTag = i != nodesReverse.Count - 1 ? $"chain-{baseTagName}-{i + 1}" : null; + if (node.ConfigType.IsGroupType()) { - break; + var childProfiles = new CoreConfigV2rayService(context with { Node = node, }).BuildGroupProxyOutbounds(currentTag); + if (!dialerProxyTag.IsNullOrEmpty()) + { + var chainEndNodes = + childProfiles.Where(n => n?.streamSettings?.sockopt?.dialerProxy?.IsNullOrEmpty() ?? true); + foreach (var chainEndNode in chainEndNodes) + { + chainEndNode.streamSettings.sockopt = new() + { + dialerProxy = dialerProxyTag + }; + } + } + if (i != 0) + { + var chainStartNodes = childProfiles.Where(n => n.tag.StartsWith(currentTag)).ToList(); + if (chainStartNodes.Count == 1) + { + foreach (var existedChainEndNode in resultOutbounds.Where(n => n.streamSettings?.sockopt?.dialerProxy == currentTag)) + { + existedChainEndNode.streamSettings.sockopt = new() + { + dialerProxy = chainStartNodes.First().tag + }; + } + } + else if (chainStartNodes.Count > 1) + { + var existedChainNodes = JsonUtils.DeepCopy(resultOutbounds); + resultOutbounds.Clear(); + var j = 0; + foreach (var chainStartNode in chainStartNodes) + { + var existedChainNodesClone = JsonUtils.DeepCopy(existedChainNodes); + foreach (var existedChainNode in existedChainNodesClone) + { + var cloneTag = $"{existedChainNode.tag}-clone-{j + 1}"; + existedChainNode.tag = cloneTag; + } + for (var k = 0; k < existedChainNodesClone.Count; k++) + { + var existedChainNode = existedChainNodesClone[k]; + var previousDialerProxyTag = existedChainNode.streamSettings?.sockopt?.dialerProxy; + var nextTag = k + 1 < existedChainNodesClone.Count + ? existedChainNodesClone[k + 1].tag + : chainStartNode.tag; + existedChainNode.streamSettings.sockopt = new() + { + dialerProxy = (previousDialerProxyTag == currentTag) + ? chainStartNode.tag + : nextTag + }; + resultOutbounds.Add(existedChainNode); + } + j++; + } + } + } + resultOutbounds.AddRange(childProfiles); + continue; } - var outbound = JsonUtils.Deserialize(txtOutbound); - var result = await GenOutbound(node, outbound); + var outbound = new CoreConfigV2rayService(context with { Node = node, }).BuildProxyOutbound(); - if (result != 0) - { - break; - } + outbound.tag = currentTag; - if (i == 0) - { - outbound.tag = baseTagName; - } - else - { - // avoid v2ray observe - outbound.tag = "chain-" + baseTagName + i.ToString(); - } - - if (i != nodesReverse.Count - 1) + if (!dialerProxyTag.IsNullOrEmpty()) { outbound.streamSettings.sockopt = new() { - dialerProxy = "chain-" + baseTagName + (i + 1).ToString() + dialerProxy = dialerProxyTag }; } resultOutbounds.Add(outbound); } - if (baseTagName == Global.ProxyTag) - { - resultOutbounds.AddRange(v2rayConfig.outbounds); - v2rayConfig.outbounds = resultOutbounds; - } - else - { - v2rayConfig.outbounds.AddRange(resultOutbounds); - } - - return await Task.FromResult(0); + return resultOutbounds; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs index 8691abb1..76969297 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayRoutingService.cs @@ -2,20 +2,20 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenRouting(V2rayConfig v2rayConfig) + private void GenRouting() { try { - if (v2rayConfig.routing?.rules != null) + if (_coreConfig.routing?.rules != null) { - v2rayConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; + _coreConfig.routing.domainStrategy = _config.RoutingBasicItem.DomainStrategy; - var routing = await ConfigHandler.GetDefaultRouting(_config); + var routing = context.RoutingItem; if (routing != null) { if (routing.DomainStrategy.IsNotEmpty()) { - v2rayConfig.routing.domainStrategy = routing.DomainStrategy; + _coreConfig.routing.domainStrategy = routing.DomainStrategy; } var rules = JsonUtils.Deserialize>(routing.RuleSet); foreach (var item in rules) @@ -31,7 +31,18 @@ public partial class CoreConfigV2rayService } var item2 = JsonUtils.Deserialize(JsonUtils.Serialize(item)); - await GenRoutingUserRule(item2, v2rayConfig); + GenRoutingUserRule(item2); + } + } + var balancerTagList = _coreConfig.routing.balancers + ?.Select(p => p.tag) + .ToList() ?? []; + if (balancerTagList.Count > 0) + { + foreach (var rulesItem in _coreConfig.routing.rules.Where(r => balancerTagList.Contains(r.outboundTag + Global.BalancerTagSuffix))) + { + rulesItem.balancerTag = rulesItem.outboundTag + Global.BalancerTagSuffix; + rulesItem.outboundTag = null; } } } @@ -40,95 +51,94 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return 0; } - private async Task GenRoutingUserRule(RulesItem4Ray? rule, V2rayConfig v2rayConfig) + private void GenRoutingUserRule(RulesItem4Ray? userRule) { try { - if (rule == null) + if (userRule == null) { - return 0; + return; } - rule.outboundTag = await GenRoutingUserRuleOutbound(rule.outboundTag, v2rayConfig); + userRule.outboundTag = GenRoutingUserRuleOutbound(userRule.outboundTag ?? Global.ProxyTag); - if (rule.port.IsNullOrEmpty()) + if (userRule.port.IsNullOrEmpty()) { - rule.port = null; + userRule.port = null; } - if (rule.network.IsNullOrEmpty()) + if (userRule.network.IsNullOrEmpty()) { - rule.network = null; + userRule.network = null; } - if (rule.domain?.Count == 0) + if (userRule.domain?.Count == 0) { - rule.domain = null; + userRule.domain = null; } - if (rule.ip?.Count == 0) + if (userRule.ip?.Count == 0) { - rule.ip = null; + userRule.ip = null; } - if (rule.protocol?.Count == 0) + if (userRule.protocol?.Count == 0) { - rule.protocol = null; + userRule.protocol = null; } - if (rule.inboundTag?.Count == 0) + if (userRule.inboundTag?.Count == 0) { - rule.inboundTag = null; + userRule.inboundTag = null; } - if (rule.process?.Count == 0) + if (userRule.process?.Count == 0) { - rule.process = null; + userRule.process = null; } var hasDomainIp = false; - if (rule.domain?.Count > 0) + if (userRule.domain?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.ip = null; it.process = null; it.type = "field"; for (var k = it.domain.Count - 1; k >= 0; k--) { - if (it.domain[k].StartsWith("#")) + if (it.domain[k].StartsWith('#')) { it.domain.RemoveAt(k); } it.domain[k] = it.domain[k].Replace(Global.RoutingRuleComma, ","); } - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } - if (rule.ip?.Count > 0) + if (userRule.ip?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.process = null; it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } - if (rule.process?.Count > 0) + if (userRule.process?.Count > 0) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.domain = null; it.ip = null; it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); hasDomainIp = true; } if (!hasDomainIp) { - if (rule.port.IsNotEmpty() - || rule.protocol?.Count > 0 - || rule.inboundTag?.Count > 0 - || rule.network != null + if (userRule.port.IsNotEmpty() + || userRule.protocol?.Count > 0 + || userRule.inboundTag?.Count > 0 + || userRule.network != null ) { - var it = JsonUtils.DeepCopy(rule); + var it = JsonUtils.DeepCopy(userRule); it.type = "field"; - v2rayConfig.routing.rules.Add(it); + _coreConfig.routing.rules.Add(it); } } } @@ -136,17 +146,16 @@ public partial class CoreConfigV2rayService { Logging.SaveLog(_tag, ex); } - return await Task.FromResult(0); } - private async Task GenRoutingUserRuleOutbound(string outboundTag, V2rayConfig v2rayConfig) + private string GenRoutingUserRuleOutbound(string outboundTag) { if (Global.OutboundTags.Contains(outboundTag)) { return outboundTag; } - var node = await AppManager.Instance.GetProfileItemViaRemarks(outboundTag); + var node = context.AllProxiesMap.GetValueOrDefault($"remark:{outboundTag}"); if (node == null || (!Global.XraySupportConfigType.Contains(node.ConfigType) @@ -156,27 +165,44 @@ public partial class CoreConfigV2rayService } var tag = $"{node.IndexId}-{Global.ProxyTag}"; - if (v2rayConfig.outbounds.Any(p => p.tag == tag)) + if (_coreConfig.outbounds.Any(p => p.tag.StartsWith(tag))) { return tag; } - if (node.ConfigType.IsGroupType()) + var proxyOutbounds = new CoreConfigV2rayService(context with { Node = node, }).BuildAllProxyOutbounds(tag); + _coreConfig.outbounds.AddRange(proxyOutbounds); + if (proxyOutbounds.Count(n => n.tag.StartsWith(tag)) > 1) { - var ret = await GenGroupOutbound(node, v2rayConfig, tag); - if (ret == 0) - { - return tag; - } - return Global.ProxyTag; + var multipleLoad = node.GetProtocolExtra().MultipleLoad ?? EMultipleLoad.LeastPing; + GenObservatory(multipleLoad, tag); + GenBalancer(multipleLoad, tag); } - var txtOutbound = EmbedUtils.GetEmbedText(Global.V2raySampleOutbound); - var outbound = JsonUtils.Deserialize(txtOutbound); - await GenOutbound(node, outbound); - outbound.tag = tag; - v2rayConfig.outbounds.Add(outbound); + return tag; + } - return outbound.tag; + private RulesItem4Ray BuildFinalRule() + { + var finalRule = new RulesItem4Ray() + { + type = "field", + network = "tcp,udp", + outboundTag = Global.ProxyTag, + }; + var balancer = + _coreConfig?.routing?.balancers?.FirstOrDefault(b => b.tag == Global.ProxyTag + Global.BalancerTagSuffix, null); + var domainStrategy = _coreConfig.routing?.domainStrategy ?? Global.AsIs; + if (balancer is not null) + { + finalRule.outboundTag = null; + finalRule.balancerTag = balancer.tag; + } + if (domainStrategy == Global.IPIfNonMatch) + { + finalRule.network = null; + finalRule.ip = ["0.0.0.0/0", "::/0"]; + } + return finalRule; } } diff --git a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs index b2ec37b4..6364b7bd 100644 --- a/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs +++ b/v2rayN/ServiceLib/Services/CoreConfig/V2ray/V2rayStatisticService.cs @@ -2,7 +2,7 @@ namespace ServiceLib.Services.CoreConfig; public partial class CoreConfigV2rayService { - private async Task GenStatistic(V2rayConfig v2rayConfig) + private void GenStatistic() { if (_config.GuiItem.EnableStatistics || _config.GuiItem.DisplayRealTimeSpeed) { @@ -11,17 +11,17 @@ public partial class CoreConfigV2rayService Policy4Ray policyObj = new(); SystemPolicy4Ray policySystemSetting = new(); - v2rayConfig.stats = new Stats4Ray(); + _coreConfig.stats = new Stats4Ray(); apiObj.tag = tag; - v2rayConfig.metrics = apiObj; + _coreConfig.metrics = apiObj; policySystemSetting.statsOutboundDownlink = true; policySystemSetting.statsOutboundUplink = true; policyObj.system = policySystemSetting; - v2rayConfig.policy = policyObj; + _coreConfig.policy = policyObj; - if (!v2rayConfig.inbounds.Exists(item => item.tag == tag)) + if (!_coreConfig.inbounds.Exists(item => item.tag == tag)) { Inbounds4Ray apiInbound = new(); Inboundsettings4Ray apiInboundSettings = new(); @@ -31,10 +31,10 @@ public partial class CoreConfigV2rayService apiInbound.protocol = Global.InboundAPIProtocol; apiInboundSettings.address = Global.Loopback; apiInbound.settings = apiInboundSettings; - v2rayConfig.inbounds.Add(apiInbound); + _coreConfig.inbounds.Add(apiInbound); } - if (!v2rayConfig.routing.rules.Exists(item => item.outboundTag == tag)) + if (!_coreConfig.routing.rules.Exists(item => item.outboundTag == tag)) { RulesItem4Ray apiRoutingRule = new() { @@ -43,9 +43,8 @@ public partial class CoreConfigV2rayService type = "field" }; - v2rayConfig.routing.rules.Add(apiRoutingRule); + _coreConfig.routing.rules.Add(apiRoutingRule); } } - return await Task.FromResult(0); } } diff --git a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs index 6e4d021c..d30917ec 100644 --- a/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddGroupServerViewModel.cs @@ -234,13 +234,6 @@ public class AddGroupServerViewModel : MyReactiveObject SelectedSource.SetProtocolExtra(protocolExtra); - var hasCycle = await GroupProfileManager.HasCycle(SelectedSource.IndexId, protocolExtra); - if (hasCycle) - { - NoticeManager.Instance.Enqueue(string.Format(ResUI.GroupSelfReference, remarks)); - return; - } - if (await ConfigHandler.AddServerCommon(_config, SelectedSource) == 0) { NoticeManager.Instance.Enqueue(ResUI.OperationSuccess); diff --git a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs index ba698c20..f56bb1c0 100644 --- a/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/AddServerViewModel.cs @@ -218,7 +218,7 @@ public class AddServerViewModel : MyReactiveObject return; } - List shaList = new(); + List shaList = []; foreach (var cert in certList) { var sha = CertPemManager.GetCertSha256Thumbprint(cert); @@ -228,7 +228,7 @@ public class AddServerViewModel : MyReactiveObject } shaList.Add(sha); } - CertSha = string.Join('~', shaList); + CertSha = string.Join(',', shaList); } private async Task FetchCert() @@ -247,11 +247,6 @@ public class AddServerViewModel : MyReactiveObject { serverName = SelectedSource.Address; } - if (!Utils.IsDomain(serverName)) - { - UpdateCertTip(ResUI.ServerNameMustBeValidDomain); - return; - } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; @@ -277,11 +272,6 @@ public class AddServerViewModel : MyReactiveObject { serverName = SelectedSource.Address; } - if (!Utils.IsDomain(serverName)) - { - UpdateCertTip(ResUI.ServerNameMustBeValidDomain); - return; - } if (SelectedSource.Port > 0) { domain += $":{SelectedSource.Port}"; diff --git a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs index 8f5e9029..becabf25 100644 --- a/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MainWindowViewModel.cs @@ -540,7 +540,14 @@ public class MainWindowViewModel : MyReactiveObject { SetReloadEnabled(false); - var msgs = await ActionPrecheckManager.Instance.Check(_config.IndexId); + var profileItem = await ConfigHandler.GetDefaultServer(_config); + if (profileItem == null) + { + NoticeManager.Instance.Enqueue(ResUI.CheckServerSettings); + return; + } + var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, profileItem); + var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); if (msgs.Count > 0) { foreach (var msg in msgs) @@ -548,12 +555,15 @@ public class MainWindowViewModel : MyReactiveObject NoticeManager.Instance.SendMessage(msg); } NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); - return; + if (!validatorResult.Success) + { + return; + } } await Task.Run(async () => { - await LoadCore(); + await LoadCore(context); await SysProxyHandler.UpdateSysProxy(_config, false); await Task.Delay(1000); }); @@ -594,10 +604,9 @@ public class MainWindowViewModel : MyReactiveObject RxApp.MainThreadScheduler.Schedule(() => BlReloadEnabled = enabled); } - private async Task LoadCore() + private async Task LoadCore(CoreConfigContext? context) { - var node = await ConfigHandler.GetDefaultServer(_config); - await CoreManager.Instance.LoadCore(node); + await CoreManager.Instance.LoadCore(context); } #endregion core job diff --git a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs index 86251873..8462a2c3 100644 --- a/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/OptionSettingViewModel.cs @@ -95,7 +95,6 @@ public class OptionSettingViewModel : MyReactiveObject [Reactive] public bool TunStrictRoute { get; set; } [Reactive] public string TunStack { get; set; } [Reactive] public int TunMtu { get; set; } - [Reactive] public bool TunEnableExInbound { get; set; } [Reactive] public bool TunEnableIPv6Address { get; set; } #endregion Tun mode @@ -220,7 +219,6 @@ public class OptionSettingViewModel : MyReactiveObject TunStrictRoute = _config.TunModeItem.StrictRoute; TunStack = _config.TunModeItem.Stack; TunMtu = _config.TunModeItem.Mtu; - TunEnableExInbound = _config.TunModeItem.EnableExInbound; TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address; #endregion Tun mode @@ -380,7 +378,6 @@ public class OptionSettingViewModel : MyReactiveObject _config.TunModeItem.StrictRoute = TunStrictRoute; _config.TunModeItem.Stack = TunStack; _config.TunModeItem.Mtu = TunMtu; - _config.TunModeItem.EnableExInbound = TunEnableExInbound; _config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address; //coreType diff --git a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs index 2d838189..1cc6f46e 100644 --- a/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ProfilesViewModel.cs @@ -788,7 +788,8 @@ public class ProfilesViewModel : MyReactiveObject return; } - var msgs = await ActionPrecheckManager.Instance.Check(item); + var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); + var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); if (msgs.Count > 0) { foreach (var msg in msgs) @@ -796,12 +797,15 @@ public class ProfilesViewModel : MyReactiveObject NoticeManager.Instance.SendMessage(msg); } NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); - return; + if (!validatorResult.Success) + { + return; + } } if (blClipboard) { - var result = await CoreConfigHandler.GenerateClientConfig(item, null); + var result = await CoreConfigHandler.GenerateClientConfig(context, null); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); @@ -824,7 +828,21 @@ public class ProfilesViewModel : MyReactiveObject { return; } - var result = await CoreConfigHandler.GenerateClientConfig(item, fileName); + var (context, validatorResult) = await CoreConfigContextBuilder.Build(_config, item); + var msgs = new List([..validatorResult.Errors, ..validatorResult.Warnings]); + if (msgs.Count > 0) + { + foreach (var msg in msgs) + { + NoticeManager.Instance.SendMessage(msg); + } + NoticeManager.Instance.Enqueue(Utils.List2String(msgs.Take(10).ToList(), true)); + if (!validatorResult.Success) + { + return; + } + } + var result = await CoreConfigHandler.GenerateClientConfig(context, fileName); if (result.Success != true) { NoticeManager.Instance.Enqueue(result.Msg); diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml index 356698fb..477eca5d 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml @@ -32,7 +32,7 @@ IsCancel="True" /> - + - + - + - + + + + + + @@ -745,7 +785,7 @@ @@ -908,7 +948,7 @@ @@ -996,7 +1036,7 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> - + diff --git a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs index 250bbe78..fdd097b2 100644 --- a/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/AddServerWindow.axaml.cs @@ -78,6 +78,7 @@ public partial class AddServerWindow : WindowBase cmbCoreType.IsEnabled = false; cmbFingerprint.IsEnabled = false; cmbFingerprint.SelectedValue = string.Empty; + gridFinalmask.IsVisible = false; cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; break; @@ -95,6 +96,7 @@ public partial class AddServerWindow : WindowBase gridAnytls.IsVisible = true; lstStreamSecurity.Add(Global.StreamSecurityReality); cmbCoreType.IsEnabled = false; + gridFinalmask.IsVisible = false; break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -194,6 +196,8 @@ public partial class AddServerWindow : WindowBase this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); diff --git a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml index 65218738..cfcb48f0 100644 --- a/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml +++ b/v2rayN/v2rayN.Desktop/Views/OptionSettingWindow.axaml @@ -325,12 +325,6 @@ Grid.Column="1" Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> - @@ -843,19 +837,6 @@ Margin="{StaticResource Margin4}" HorizontalAlignment="Left" /> - - - this.Bind(ViewModel, vm => vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.SelectedValue).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml b/v2rayN/v2rayN/Views/AddServerWindow.xaml index 8c6b4a35..e78e7f9b 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml @@ -60,6 +60,8 @@ + + @@ -928,12 +930,47 @@ Text="{x:Static resx:ResUI.TbPath}" /> + + + + + + + + + + + + + - + @@ -963,7 +1000,7 @@ @@ -1152,7 +1189,7 @@ @@ -1264,7 +1301,7 @@ Style="{StaticResource DefTextBox}" /> diff --git a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs index 86d9cac1..4875afa5 100644 --- a/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs +++ b/v2rayN/v2rayN/Views/AddServerWindow.xaml.cs @@ -73,6 +73,7 @@ public partial class AddServerWindow cmbCoreType.IsEnabled = false; cmbFingerprint.IsEnabled = false; cmbFingerprint.Text = string.Empty; + gridFinalmask.Visibility = Visibility.Collapsed; cmbHeaderType8.ItemsSource = Global.TuicCongestionControls; break; @@ -90,6 +91,7 @@ public partial class AddServerWindow gridAnytls.Visibility = Visibility.Visible; cmbCoreType.IsEnabled = false; lstStreamSecurity.Add(Global.StreamSecurityReality); + gridFinalmask.Visibility = Visibility.Collapsed; break; } cmbStreamSecurity.ItemsSource = lstStreamSecurity; @@ -190,6 +192,8 @@ public partial class AddServerWindow this.Bind(ViewModel, vm => vm.SelectedSource.SpiderX, v => v.txtSpiderX.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SelectedSource.Mldsa65Verify, v => v.txtMldsa65Verify.Text).DisposeWith(disposables); + this.Bind(ViewModel, vm => vm.SelectedSource.Finalmask, v => v.txtFinalmask.Text).DisposeWith(disposables); + this.BindCommand(ViewModel, vm => vm.FetchCertCmd, v => v.btnFetchCert).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.FetchCertChainCmd, v => v.btnFetchCertChain).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.SaveCmd, v => v.btnSave).DisposeWith(disposables); diff --git a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml index 00b2dc9e..8f0d24d4 100644 --- a/v2rayN/v2rayN/Views/OptionSettingWindow.xaml +++ b/v2rayN/v2rayN/Views/OptionSettingWindow.xaml @@ -391,13 +391,6 @@ Grid.Column="1" Margin="{StaticResource Margin8}" HorizontalAlignment="Left" /> - @@ -1097,20 +1090,6 @@ HorizontalAlignment="Left" Style="{StaticResource DefComboBox}" /> - - - vm.TunStrictRoute, v => v.togStrictRoute.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunStack, v => v.cmbStack.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.Text).DisposeWith(disposables); - this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.Text).DisposeWith(disposables);